diff --git a/.surface b/.surface index ec59b74f..184dd58f 100644 --- a/.surface +++ b/.surface @@ -873,6 +873,7 @@ CMD basecamp lineup list CMD basecamp lineup update CMD basecamp login CMD basecamp logout +CMD basecamp mcp CMD basecamp me CMD basecamp messageboards CMD basecamp messageboards show @@ -10701,6 +10702,29 @@ FLAG basecamp logout --stats type=bool FLAG basecamp logout --styled type=bool FLAG basecamp logout --todolist type=string FLAG basecamp logout --verbose type=count +FLAG basecamp mcp --account type=string +FLAG basecamp mcp --agent type=bool +FLAG basecamp mcp --cache-dir type=string +FLAG basecamp mcp --count type=bool +FLAG basecamp mcp --domains type=stringSlice +FLAG basecamp mcp --help type=bool +FLAG basecamp mcp --hints type=bool +FLAG basecamp mcp --ids-only type=bool +FLAG basecamp mcp --in type=string +FLAG basecamp mcp --jq type=string +FLAG basecamp mcp --json type=bool +FLAG basecamp mcp --markdown type=bool +FLAG basecamp mcp --md type=bool +FLAG basecamp mcp --no-hints type=bool +FLAG basecamp mcp --no-stats type=bool +FLAG basecamp mcp --profile type=string +FLAG basecamp mcp --project type=string +FLAG basecamp mcp --quiet type=bool +FLAG basecamp mcp --read-only type=bool +FLAG basecamp mcp --stats type=bool +FLAG basecamp mcp --styled type=bool +FLAG basecamp mcp --todolist type=string +FLAG basecamp mcp --verbose type=count FLAG basecamp me --account type=string FLAG basecamp me --agent type=bool FLAG basecamp me --cache-dir type=string @@ -18137,6 +18161,7 @@ SUB basecamp lineup list SUB basecamp lineup update SUB basecamp login SUB basecamp logout +SUB basecamp mcp SUB basecamp me SUB basecamp messageboards SUB basecamp messageboards show diff --git a/README.md b/README.md index e2f023a2..46567c54 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,31 @@ To pick up a newer plugin version later, refresh the marketplace with See [install.md](install.md) for step-by-step setup instructions. +### MCP Server + +`basecamp mcp` runs an MCP (Model Context Protocol) server on stdin/stdout, +serving Basecamp as tools backed by your signed-in account — no separate +binary or extra credentials. Register it with any MCP client as a stdio +server: + +```bash +claude mcp add basecamp -- basecamp mcp +``` + +Fifteen domain tools (`basecamp_projects`, `basecamp_todos`, `basecamp_cards`, +`basecamp_messages`, `basecamp_campfires`, `basecamp_boosts`, +`basecamp_schedules`, `basecamp_files`, `basecamp_people`, +`basecamp_automation`, `basecamp_reports`, `basecamp_everything`, +`basecamp_clientside`, `basecamp_forwards`, `basecamp_account`) cover the +Basecamp API, scoped to the configured account. Each tool takes +`{"action": "...", "params": {...}}` and serves per-action schemas through +its `describe` action. + +```bash +basecamp mcp --read-only # serve only read-only actions +basecamp mcp --domains projects,todos # narrow the served surface (fails closed on unknown keys) +``` + ## Configuration ``` diff --git a/e2e/smoke/smoke_lifecycle.bats b/e2e/smoke/smoke_lifecycle.bats index 283b9808..5eef8b23 100644 --- a/e2e/smoke/smoke_lifecycle.bats +++ b/e2e/smoke/smoke_lifecycle.bats @@ -65,6 +65,10 @@ load smoke_helper mark_out_of_scope "Raw API passthrough — tested via specific commands" } +@test "mcp is out of scope" { + mark_out_of_scope "Long-running MCP stdio server — covered by Go wire tests in internal/mcpserver and internal/commands" +} + @test "skill install is out of scope" { mark_out_of_scope "Modifies Claude Code config" } diff --git a/go.mod b/go.mod index c41f2fb9..4fe2233d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( charm.land/lipgloss/v2 v2.0.6 github.com/basecamp/basecamp-sdk/go v0.14.0 github.com/basecamp/cli v0.2.2-0.20260828032417-844e9f965144 + github.com/basecamp/mcp v0.0.0-20260828100356-2d6f44b51e9d github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/glamour v1.0.0 github.com/charmbracelet/huh v1.0.0 @@ -16,6 +17,7 @@ require ( github.com/fsnotify/fsnotify v1.10.1 github.com/gofrs/flock v0.13.0 github.com/itchyny/gojq v0.12.19 + github.com/modelcontextprotocol/go-sdk v1.7.0 github.com/sigstore/sigstore-go v1.3.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 @@ -86,6 +88,7 @@ require ( github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/google/certificate-transparency-go v1.3.3 // indirect github.com/google/go-containerregistry v0.21.7 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect @@ -109,6 +112,8 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.4 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/protobuf-specs v0.5.1 // indirect github.com/sigstore/rekor v1.5.3 // indirect @@ -119,6 +124,7 @@ require ( github.com/transparency-dev/formats v0.1.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yuin/goldmark-emoji v1.0.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -128,8 +134,10 @@ require ( go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/net v0.57.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/term v0.45.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect google.golang.org/grpc v1.82.1 // indirect diff --git a/go.sum b/go.sum index 944469b2..f18cbab1 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/basecamp/basecamp-sdk/go v0.14.0 h1:Jyzmu3ucqwVe+YNC1zWJBOdPjBaTapyZW github.com/basecamp/basecamp-sdk/go v0.14.0/go.mod h1:BlEtZTW78rOY5geVtKaCiccYT9Jz+QGgPuXLo2pROWk= github.com/basecamp/cli v0.2.2-0.20260828032417-844e9f965144 h1:ZJxdRGsb6QyuXNFge6wVx7772e8kDHC9a72aBokaTwM= github.com/basecamp/cli v0.2.2-0.20260828032417-844e9f965144/go.mod h1:iTBTaWvsPEFIcZfkxQHEfISyJ6sZ7036K6bNx0RY3EE= +github.com/basecamp/mcp v0.0.0-20260828100356-2d6f44b51e9d h1:zEQVGq1x1nhKMZ2TudFAcSJ32CHT8richI1vQakIKz4= +github.com/basecamp/mcp v0.0.0-20260828100356-2d6f44b51e9d/go.mod h1:Ee2c/q1/pg+5T5741PIuA3s6VJMQC7I0XBNXIHIujzA= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= @@ -248,6 +250,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= @@ -328,6 +332,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4 github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -367,6 +373,10 @@ github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgm github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= @@ -423,6 +433,8 @@ github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= @@ -482,6 +494,8 @@ golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik= diff --git a/internal/cli/root.go b/internal/cli/root.go index 28f4b810..a4267300 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -355,6 +355,7 @@ func Execute() { cmd.AddCommand(commands.NewTUICmd()) cmd.AddCommand(commands.NewBonfireCmd()) cmd.AddCommand(commands.NewAgentHookCmd()) + cmd.AddCommand(commands.NewMCPCmd()) // Tier-2 stdin guard: reject a stray literal "-" when stdin is piped, // everywhere a command doesn't explicitly accept it — except cobra's diff --git a/internal/commands/commands.go b/internal/commands/commands.go index ee18659a..56474201 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -143,6 +143,7 @@ func CommandCategories() []CommandCategory { {Name: "tui", Category: "additional", Description: "Launch the Basecamp workspace", Experimental: true, DevOnly: true}, {Name: "bonfire", Category: "additional", Description: "Multi-chat orchestration", Actions: []string{"split", "layout"}, Experimental: true, DevOnly: true}, {Name: "api", Category: "additional", Description: "Raw API access"}, + {Name: "mcp", Category: "additional", Description: "Serve Basecamp to MCP clients over stdio"}, {Name: "help", Category: "additional", Description: "Show help"}, {Name: "version", Category: "additional", Description: "Show version"}, }, diff --git a/internal/commands/commands_test.go b/internal/commands/commands_test.go index 85336c48..50e4da03 100644 --- a/internal/commands/commands_test.go +++ b/internal/commands/commands_test.go @@ -119,6 +119,7 @@ func buildRootWithAllCommands() *cobra.Command { root.AddCommand(commands.NewTUICmd()) root.AddCommand(commands.NewProfileCmd()) root.AddCommand(commands.NewBonfireCmd()) + root.AddCommand(commands.NewMCPCmd()) commands.InstallDashGuard(root) root.InitDefaultHelpCmd() return root diff --git a/internal/commands/mcp.go b/internal/commands/mcp.go new file mode 100644 index 00000000..811a6342 --- /dev/null +++ b/internal/commands/mcp.go @@ -0,0 +1,78 @@ +package commands + +import ( + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/spf13/cobra" + + "github.com/basecamp/basecamp-cli/internal/appctx" + "github.com/basecamp/basecamp-cli/internal/mcpserver" + "github.com/basecamp/basecamp-cli/internal/output" +) + +// mcpTransport is a seam so tests can drive the server over in-memory +// transports instead of the process's stdin/stdout. +var mcpTransport = func() mcp.Transport { return &mcp.StdioTransport{} } + +// NewMCPCmd creates the mcp command serving Basecamp over MCP on stdio. +func NewMCPCmd() *cobra.Command { + var readOnly bool + var domains []string + + cmd := &cobra.Command{ + Use: "mcp", + Short: "Serve Basecamp to MCP clients over stdio", + Long: "Run an MCP (Model Context Protocol) server on stdin/stdout, serving Basecamp\n" + + "projects, todos, cards, messages, and more as tools backed by your signed-in\n" + + "account.\n\n" + + "Register it with an MCP client as a stdio server, e.g.:\n\n" + + " claude mcp add basecamp -- basecamp mcp", + Example: ` basecamp mcp + basecamp mcp --read-only + basecamp mcp --domains projects,todos,cards`, + Args: cobra.NoArgs, + Annotations: map[string]string{ + "agent_notes": "Long-running server; stdout speaks the MCP wire protocol. Not for interactive use.", + }, + RunE: func(cmd *cobra.Command, args []string) error { + app := appctx.FromContext(cmd.Context()) + + if !app.Auth.IsAuthenticated() { + return output.ErrAuth("Not authenticated. Run: basecamp auth login") + } + // Stdio belongs to the MCP wire, so the account cannot be + // resolved interactively: require it configured up front, like + // any other account-scoped command running non-interactively. + if err := app.RequireAccount(); err != nil { + return err + } + + srv, err := mcpserver.New(app.Account(), mcpserver.Config{ReadOnly: readOnly, Domains: domains}) + if err != nil { + return err + } + + ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM) + defer stop() + + // Log to stderr: stdout belongs to the MCP wire. + logger := slog.New(slog.NewTextHandler(cmd.ErrOrStderr(), nil)) + session, err := srv.BuildMCPServer(logger).Connect(ctx, mcpTransport(), nil) + if err != nil { + return err + } + logger.Info("MCP server running on stdio", "tools", len(srv.Domains()), "read_only", readOnly) + + return session.Wait() + }, + } + + cmd.Flags().BoolVar(&readOnly, "read-only", false, "Serve only read-only actions") + cmd.Flags().StringSliceVar(&domains, "domains", nil, "Narrow to specific domains (comma-separated; default all)") + + return cmd +} diff --git a/internal/commands/mcp_test.go b/internal/commands/mcp_test.go new file mode 100644 index 00000000..8df1485d --- /dev/null +++ b/internal/commands/mcp_test.go @@ -0,0 +1,177 @@ +package commands + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/basecamp/basecamp-cli/internal/appctx" + "github.com/basecamp/basecamp-cli/internal/config" +) + +func TestMCPCommandFlags(t *testing.T) { + cmd := NewMCPCmd() + assert.Equal(t, "mcp", cmd.Name()) + + readOnly := cmd.Flags().Lookup("read-only") + require.NotNil(t, readOnly) + assert.Equal(t, "false", readOnly.DefValue, "full surface is the default, matching basecamp-mcp-server") + require.NotNil(t, cmd.Flags().Lookup("domains")) +} + +// setupMCPTestApp builds the app the way the root command would: real +// config, real auth manager (reading BASECAMP_TOKEN), real SDK client. +func setupMCPTestApp(t *testing.T, accountID, baseURL string) *appctx.App { + t.Helper() + t.Setenv("BASECAMP_NO_KEYRING", "1") + t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + t.Setenv("XDG_STATE_HOME", t.TempDir()) + t.Setenv("XDG_CACHE_HOME", t.TempDir()) + + cfg := config.Default() + cfg.AccountID = accountID + cfg.BaseURL = baseURL + cfg.CacheDir = t.TempDir() + return appctx.NewApp(cfg) +} + +func executeMCPCommand(t *testing.T, app *appctx.App, args ...string) error { + t.Helper() + cmd := NewMCPCmd() + cmd.SetArgs(args) + cmd.SetContext(appctx.WithApp(context.Background(), app)) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + return cmd.Execute() +} + +func TestMCPCommandRequiresAuth(t *testing.T) { + t.Setenv("BASECAMP_TOKEN", "") + app := setupMCPTestApp(t, "999", "https://3.basecampapi.com") + + err := executeMCPCommand(t, app) + require.Error(t, err) + assert.Contains(t, err.Error(), "Not authenticated") +} + +func TestMCPCommandRequiresAccount(t *testing.T) { + t.Setenv("BASECAMP_TOKEN", "test-token") + app := setupMCPTestApp(t, "", "https://3.basecampapi.com") + + err := executeMCPCommand(t, app) + require.Error(t, err) + assert.Contains(t, err.Error(), "Account ID required") +} + +func TestMCPCommandFailsClosedOnUnknownDomain(t *testing.T) { + t.Setenv("BASECAMP_TOKEN", "test-token") + app := setupMCPTestApp(t, "999", "https://3.basecampapi.com") + + err := executeMCPCommand(t, app, "--domains", "projects,bogus") + require.Error(t, err) + assert.Contains(t, err.Error(), `unknown domain "bogus"`) +} + +// stubMCPTransport swaps the stdio transport for the server side of an +// in-memory pipe and returns the client side. +func stubMCPTransport(t *testing.T) mcp.Transport { + t.Helper() + clientTransport, serverTransport := mcp.NewInMemoryTransports() + orig := mcpTransport + mcpTransport = func() mcp.Transport { return serverTransport } + t.Cleanup(func() { mcpTransport = orig }) + return clientTransport +} + +// runMCPCommand runs `basecamp mcp` against a stub Basecamp server and +// connects a real MCP client to it over the transport seam. The command +// exits when the client session closes. +func runMCPCommand(t *testing.T, upstream *httptest.Server, args ...string) *mcp.ClientSession { + t.Helper() + t.Setenv("BASECAMP_TOKEN", "test-token") + app := setupMCPTestApp(t, "999", upstream.URL) + clientTransport := stubMCPTransport(t) + + done := make(chan error, 1) + go func() { done <- executeMCPCommand(t, app, args...) }() + t.Cleanup(func() { + require.NoError(t, <-done, "basecamp mcp exited with error") + }) + + client := mcp.NewClient(&mcp.Implementation{Name: "test-client", Version: "0.0.0"}, nil) + session, err := client.Connect(context.Background(), clientTransport, nil) + require.NoError(t, err, "MCP initialize failed") + t.Cleanup(func() { _ = session.Close() }) + return session +} + +func TestMCPCommandServesMCP(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet || r.URL.Path != "/999/projects.json" { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + return + } + // Tool calls must ride on the CLI's own credentials. + assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"), "want the CLI's token") + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[{"id":1,"name":"Making a Podcast"}]`)) + })) + t.Cleanup(upstream.Close) + + session := runMCPCommand(t, upstream) + + assert.Equal(t, "basecamp-cli", session.InitializeResult().ServerInfo.Name) + + var names []string + for tool, err := range session.Tools(context.Background(), nil) { + require.NoError(t, err) + names = append(names, tool.Name) + } + assert.Len(t, names, 15, "tools = %v", names) + + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "basecamp_projects", + Arguments: map[string]any{"action": "list_projects", "params": map[string]any{}}, + }) + require.NoError(t, err) + require.False(t, result.IsError, "list_projects failed: %v", result.Content) + text, ok := result.Content[0].(*mcp.TextContent) + require.True(t, ok, "content = %T", result.Content[0]) + var projects []struct { + Name string `json:"name"` + } + require.NoError(t, json.Unmarshal([]byte(text.Text), &projects)) + require.NotEmpty(t, projects) + assert.Equal(t, "Making a Podcast", projects[0].Name) +} + +func TestMCPCommandFlagPassthrough(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + })) + t.Cleanup(upstream.Close) + + session := runMCPCommand(t, upstream, "--read-only", "--domains", "projects") + + var tools []*mcp.Tool + for tool, err := range session.Tools(context.Background(), nil) { + require.NoError(t, err) + tools = append(tools, tool) + } + require.Len(t, tools, 1) + assert.Equal(t, "basecamp_projects", tools[0].Name) + assert.True(t, strings.Contains(tools[0].Description, "list_projects"), + "read-only basecamp_projects lost its read actions") + assert.False(t, strings.Contains(tools[0].Description, "create_project"), + "read-only basecamp_projects still lists a write action") +} diff --git a/internal/mcpserver/catalog.go b/internal/mcpserver/catalog.go new file mode 100644 index 00000000..e75ab089 --- /dev/null +++ b/internal/mcpserver/catalog.go @@ -0,0 +1,79 @@ +// Package mcpserver assembles the MCP server behind `basecamp mcp`: +// Basecamp's tool catalog derived from basecamp-sdk's model exports, +// dispatched through the CLI's authenticated, account-scoped SDK client. +// +// The generic machinery — joining behavior-model.json with openapi.json, +// rendering domain gateway tools, action dispatch, read-only filtering, the +// in-band describe action — lives in the shared toolkit at +// github.com/basecamp/mcp. This package supplies the product half: the +// curated DomainSpecs mapping basecamp-sdk tags to domains, the vendored +// model snapshot under model/ (synced by scripts/sync-mcp-model.sh, +// provenance recorded), and the dispatcher that turns catalog operations +// into basecamp-sdk requests. +package mcpserver + +import ( + "embed" + "fmt" + "io/fs" + "strings" + + "github.com/basecamp/mcp/catalog" +) + +//go:embed model/behavior-model.json model/openapi.json +var modelFS embed.FS + +// loadCatalog derives Basecamp's catalog from the embedded model snapshot, +// with every operation rescoped to the CLI's configured account. +func loadCatalog() (*catalog.Catalog, error) { + model, err := fs.Sub(modelFS, "model") + if err != nil { + return nil, fmt.Errorf("embedded model: %w", err) + } + cat, err := catalog.Load(catalog.Spec{ + ToolPrefix: "basecamp_", + Domains: DomainSpecs, + Model: model, + }) + if err != nil { + return nil, err + } + if err := rescopeToAccount(cat); err != nil { + return nil, err + } + return cat, nil +} + +// rescopeToAccount removes the accountId path parameter from every +// operation: the CLI's account-scoped SDK client supplies the account, the +// same way every other basecamp command does, so MCP callers never pass one +// and describe never advertises one. The SDK export declares accountId on +// every operation; an operation without it means the model changed shape +// under us, so fail loudly rather than serve a parameter the dispatcher +// would ignore. +func rescopeToAccount(cat *catalog.Catalog) error { + const prefix = "/{accountId}" + for _, d := range cat.Domains { + for _, op := range d.Operations { + if !strings.HasPrefix(op.Path, prefix+"/") { + return fmt.Errorf("operation %q path %q is not account-scoped", op.ID, op.Path) + } + params := op.Params[:0] + found := false + for _, p := range op.Params { + if p.In == "path" && p.Name == "accountId" { + found = true + continue + } + params = append(params, p) + } + if !found { + return fmt.Errorf("operation %q declares no accountId path parameter", op.ID) + } + op.Path = strings.TrimPrefix(op.Path, prefix) + op.Params = params + } + } + return nil +} diff --git a/internal/mcpserver/catalog_test.go b/internal/mcpserver/catalog_test.go new file mode 100644 index 00000000..b57a49b8 --- /dev/null +++ b/internal/mcpserver/catalog_test.go @@ -0,0 +1,121 @@ +package mcpserver + +import ( + "encoding/json" + "os" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/basecamp/mcp/catalog" + "github.com/basecamp/mcp/mcptest" +) + +func loadForTest(t *testing.T) *catalog.Catalog { + t.Helper() + cat, err := loadCatalog() + require.NoError(t, err, "catalog must derive cleanly from the vendored model") + return cat +} + +func TestCatalogServesCuratedDomains(t *testing.T) { + cat := loadForTest(t) + + tools := make([]string, 0, len(cat.Domains)) + for _, d := range cat.Domains { + tools = append(tools, d.Tool) + assert.NotEmpty(t, d.Operations, "domain %q has no operations", d.Key) + } + assert.Equal(t, []string{ + "basecamp_projects", "basecamp_todos", "basecamp_cards", "basecamp_messages", + "basecamp_campfires", "basecamp_boosts", "basecamp_schedules", "basecamp_files", + "basecamp_people", "basecamp_automation", "basecamp_reports", "basecamp_everything", + "basecamp_clientside", "basecamp_forwards", "basecamp_account", + }, tools) +} + +// TestCatalogClaimsEveryTag fails when the SDK grows a tag nobody has +// decided about: mapping it in DomainSpecs is the deliberate act. +func TestCatalogClaimsEveryTag(t *testing.T) { + cat := loadForTest(t) + assert.Empty(t, cat.Unmapped, "every SDK tag must be claimed by a DomainSpec") +} + +// TestCatalogExcludesBinaryUploads pins the operations the sync script drops +// from the vendored model: raw-binary uploads can't ride the JSON tool-call +// convention. Uploads stay a CLI affair (basecamp attach / upload). +func TestCatalogExcludesBinaryUploads(t *testing.T) { + cat := loadForTest(t) + excluded := map[string]bool{ + "CreateAttachment": true, + "CreateCampfireUpload": true, + "UpdateAccountLogo": true, + } + total := 0 + for _, d := range cat.Domains { + for _, op := range d.Operations { + total++ + assert.False(t, excluded[op.ID], "operation %q should be excluded from the vendored model", op.ID) + } + } + assert.Equal(t, 247, total, "served operation count") +} + +// TestCatalogIsAccountScoped pins the rescope: the CLI's account-scoped SDK +// client supplies the account, so no served operation asks the caller for an +// accountId or carries one in its path template. +func TestCatalogIsAccountScoped(t *testing.T) { + cat := loadForTest(t) + for _, d := range cat.Domains { + for _, op := range d.Operations { + assert.NotContains(t, op.Path, "{accountId}", "operation %q path", op.ID) + require.True(t, strings.HasPrefix(op.Path, "/"), "operation %q path %q", op.ID, op.Path) + for _, p := range op.Params { + assert.NotEqual(t, "accountId", p.Name, "operation %q still declares accountId", op.ID) + } + } + } +} + +// TestCatalogSnapshot renders the full served surface — every tool +// description, action, and flag — so a model sync or curation change shows +// its whole effect as a reviewable diff. Regenerate with -update. +func TestCatalogSnapshot(t *testing.T) { + cat := loadForTest(t) + + var b strings.Builder + for _, d := range cat.Domains { + b.WriteString("== " + d.Tool + "\n") + b.WriteString(d.Description()) + b.WriteString("\n") + } + mcptest.Snapshot(t, "testdata/catalog_snapshot.txt", []byte(b.String())) +} + +// TestCatalogModelProvenance keeps the vendored snapshot in lockstep with +// the basecamp-sdk release the CLI builds against: a go.mod bump without a +// snapshot refresh (or vice versa) fails here, so MCP never advertises +// routes from a different SDK version than the one linked in. +func TestCatalogModelProvenance(t *testing.T) { + data, err := os.ReadFile("model/PROVENANCE.json") + require.NoError(t, err) + var provenance struct { + Source string `json:"source"` + Commit string `json:"commit"` + Ref string `json:"ref"` + Files []string `json:"files"` + } + require.NoError(t, json.Unmarshal(data, &provenance)) + assert.Equal(t, "github.com/basecamp/basecamp-sdk", provenance.Source) + assert.NotEmpty(t, provenance.Commit) + + gomod, err := os.ReadFile("../../go.mod") + require.NoError(t, err) + match := regexp.MustCompile(`github\.com/basecamp/basecamp-sdk/go (v\S+)`).FindSubmatch(gomod) + require.NotNil(t, match, "basecamp-sdk dependency not found in go.mod") + assert.Equal(t, "go/"+string(match[1]), provenance.Ref, + "vendored model must match the basecamp-sdk version go.mod pins — run scripts/sync-mcp-model.sh against that checkout") +} diff --git a/internal/mcpserver/dispatch.go b/internal/mcpserver/dispatch.go new file mode 100644 index 00000000..92d0e4d1 --- /dev/null +++ b/internal/mcpserver/dispatch.go @@ -0,0 +1,244 @@ +package mcpserver + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/basecamp/basecamp-sdk/go/pkg/basecamp" + + "github.com/basecamp/mcp/catalog" + "github.com/basecamp/mcp/gateway" +) + +// API is the slice of the basecamp-sdk client the dispatcher drives. The +// CLI's *basecamp.AccountClient satisfies it; the client carries auth, token +// refresh, retry, account scoping, and base URL resolution, so the +// dispatcher only assembles paths and bodies. +type API interface { + Get(ctx context.Context, path string) (*basecamp.Response, error) + Post(ctx context.Context, path string, body any) (*basecamp.Response, error) + Put(ctx context.Context, path string, body any) (*basecamp.Response, error) + Delete(ctx context.Context, path string) (*basecamp.Response, error) +} + +// The CLI hands its account-scoped client straight to New. +var _ API = (*basecamp.AccountClient)(nil) + +// dispatcher turns catalog operations into basecamp-sdk requests. +// +// Calling convention: the tool call's params object carries the operation's +// path and query parameters by name, and every remaining entry becomes a +// request body property. The describe action serves the schema for all +// three. Failures are in-band isError results per MCP convention. +type dispatcher struct { + api API +} + +func (d dispatcher) handle(ctx context.Context, dom gateway.Domain, op gateway.Operation, params map[string]any) (*mcp.CallToolResult, error) { + domain, ok := dom.(*catalog.Domain) + if !ok { + return gateway.ErrorResult("internal error: domain %q is not a catalog domain", dom.Name()), nil + } + full, ok := domain.Operation(op.Action) + if !ok { + return gateway.ErrorResult("internal error: action %q not in domain %q", op.Action, dom.Name()), nil + } + + path, body, err := buildRequest(full, params) + if err != nil { + return gateway.ErrorResult("%v", err), nil + } + + resp, err := d.call(ctx, full.Method, path, body) + if err != nil { + return gateway.ErrorResult("%v", err), nil + } + if data := bytes.TrimSpace(resp.Data); len(data) == 0 || bytes.Equal(data, []byte("null")) { + // Completions, repositions, and deletes answer 204 with no body + // (the SDK normalizes those to JSON null); surface the status so + // the caller knows the write landed. + result := map[string]any{"status": resp.StatusCode} + if location := resp.Headers.Get("Location"); location != "" { + result["location"] = location + } + return gateway.JSONResult(result) + } + if next := nextPage(resp.Headers); next != "" { + // Paginated listings surface the Link rel="next" page to pass back + // as the action's page parameter. + wrapped, err := json.Marshal(map[string]any{"next_page": next, "results": resp.Data}) + if err == nil { + return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(wrapped)}}}, nil + } + } + return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(resp.Data)}}}, nil +} + +// nextPage extracts the page parameter from a geared_pagination Link +// rel="next" header. Basecamp pages by number: when a listing has more, the +// result is wrapped as {"next_page": N, "results": ...} and the caller +// passes N back as the action's page parameter. +func nextPage(headers http.Header) string { + for _, link := range headers.Values("Link") { + for part := range strings.SplitSeq(link, ",") { + if !strings.Contains(part, `rel="next"`) { + continue + } + start := strings.Index(part, "<") + end := strings.Index(part, ">") + if start < 0 || end <= start+1 { + continue + } + u, err := url.Parse(part[start+1 : end]) + if err != nil { + continue + } + if page := u.Query().Get("page"); page != "" { + return page + } + } + } + return "" +} + +func (d dispatcher) call(ctx context.Context, method, path string, body any) (*basecamp.Response, error) { + switch method { + case "GET": + return d.api.Get(ctx, path) + case "POST": + return d.api.Post(ctx, path, body) + case "PUT": + return d.api.Put(ctx, path, body) + case "DELETE": + // No catalog DELETE takes a body; buildRequest already rejected + // stray params for body-less operations. + return d.api.Delete(ctx, path) + default: + return nil, fmt.Errorf("internal error: unsupported method %s", method) + } +} + +// buildRequest resolves the operation's path template and query string from +// params and gathers the remaining entries into the request body. Missing +// path parameters, stray parameters, and non-scalar path or query values are +// errors pointing at the describe action. +func buildRequest(op *catalog.Operation, params map[string]any) (string, any, error) { + consumed := map[string]bool{} + + path := op.Path + for _, p := range op.Params { + if p.In != "path" { + continue + } + raw, ok := params[p.Name] + if !ok { + return "", nil, fmt.Errorf("missing required path parameter %q for action %q (describe the action for its schema)", p.Name, op.Action) + } + value, err := scalarString(raw) + if err != nil { + return "", nil, fmt.Errorf("path parameter %q: %w", p.Name, err) + } + path = strings.ReplaceAll(path, "{"+p.Name+"}", url.PathEscape(value)) + consumed[p.Name] = true + } + + query := url.Values{} + for _, p := range op.Params { + if p.In != "query" { + continue + } + raw, ok := params[p.Name] + if !ok { + if p.Required { + return "", nil, fmt.Errorf("missing required query parameter %q for action %q (describe the action for its schema)", p.Name, op.Action) + } + continue + } + // Rails-style array parameters (assignee_ids[], type_names[], ...) + // repeat the bracketed name once per element. + if items, isArray := raw.([]any); isArray { + if !strings.HasSuffix(p.Name, "[]") { + return "", nil, fmt.Errorf("query parameter %q: must be a string, number, or boolean, got %T", p.Name, raw) + } + for _, item := range items { + value, err := scalarString(item) + if err != nil { + return "", nil, fmt.Errorf("query parameter %q: %w", p.Name, err) + } + query.Add(p.Name, value) + } + } else { + value, err := scalarString(raw) + if err != nil { + return "", nil, fmt.Errorf("query parameter %q: %w", p.Name, err) + } + query.Set(p.Name, value) + } + consumed[p.Name] = true + } + + body := map[string]any{} + for name, value := range params { + if consumed[name] { + continue + } + if op.Body == nil { + return "", nil, fmt.Errorf("unknown parameter %q for action %q (describe the action for its schema)", name, op.Action) + } + if !bodyAllows(op, name) { + return "", nil, fmt.Errorf("unknown parameter %q for action %q (describe the action for its body schema)", name, op.Action) + } + body[name] = value + } + + if len(query) > 0 { + path += "?" + query.Encode() + } + if len(body) == 0 && (op.Body == nil || !op.BodyRequired) { + return path, nil, nil + } + return path, body, nil +} + +// bodyAllows reports whether the operation's body schema accepts a property +// named name. A schema without declared properties passes everything +// through; otherwise unknown names are rejected unless additionalProperties +// allows them. This is a typo guard, not schema validation: types, required +// properties, and nested constraints are the API's to enforce, and its +// errors come back in-band. +func bodyAllows(op *catalog.Operation, name string) bool { + properties, ok := op.Body["properties"].(map[string]any) + if !ok { + return true + } + if _, ok := properties[name]; ok { + return true + } + if extra, present := op.Body["additionalProperties"]; present { + allowed, isBool := extra.(bool) + return !isBool || allowed + } + return false +} + +// scalarString renders a JSON-decoded path or query value for the wire. +func scalarString(value any) (string, error) { + switch v := value.(type) { + case string: + return v, nil + case bool: + return strconv.FormatBool(v), nil + case float64: + return strconv.FormatFloat(v, 'f', -1, 64), nil + default: + return "", fmt.Errorf("must be a string, number, or boolean, got %T", value) + } +} diff --git a/internal/mcpserver/dispatch_test.go b/internal/mcpserver/dispatch_test.go new file mode 100644 index 00000000..3acd5daa --- /dev/null +++ b/internal/mcpserver/dispatch_test.go @@ -0,0 +1,190 @@ +package mcpserver + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/basecamp/mcp/catalog" +) + +func TestBuildRequestPathAndQuery(t *testing.T) { + op := &catalog.Operation{ + Action: "list_todos", + Method: "GET", + Path: "/buckets/{projectId}/todolists/{todolistId}/todos.json", + Params: []catalog.Param{ + {Name: "projectId", In: "path", Required: true, Schema: map[string]any{"type": "string"}}, + {Name: "todolistId", In: "path", Required: true, Schema: map[string]any{"type": "string"}}, + {Name: "status", In: "query", Schema: map[string]any{"type": "string"}}, + {Name: "page", In: "query", Schema: map[string]any{"type": "integer"}}, + }, + } + + t.Run("substitutes and encodes", func(t *testing.T) { + path, body, err := buildRequest(op, map[string]any{ + "projectId": float64(123), + "todolistId": "456", + "status": "completed", + "page": float64(2), + }) + require.NoError(t, err) + assert.Equal(t, "/buckets/123/todolists/456/todos.json?page=2&status=completed", path) + assert.Nil(t, body) + }) + + t.Run("optional query params may be omitted", func(t *testing.T) { + path, _, err := buildRequest(op, map[string]any{"projectId": "1", "todolistId": "2"}) + require.NoError(t, err) + assert.Equal(t, "/buckets/1/todolists/2/todos.json", path) + }) + + t.Run("path params are escaped", func(t *testing.T) { + path, _, err := buildRequest(op, map[string]any{"projectId": "a/b", "todolistId": "2"}) + require.NoError(t, err) + assert.Equal(t, "/buckets/a%2Fb/todolists/2/todos.json", path) + }) + + t.Run("array values on non-array params are rejected", func(t *testing.T) { + _, _, err := buildRequest(op, map[string]any{ + "projectId": "1", "todolistId": "2", "status": []any{"a", "b"}, + }) + assert.ErrorContains(t, err, `query parameter "status"`) + }) + + t.Run("missing path param", func(t *testing.T) { + _, _, err := buildRequest(op, map[string]any{"projectId": "1"}) + assert.ErrorContains(t, err, `missing required path parameter "todolistId"`) + }) + + t.Run("non-scalar path param", func(t *testing.T) { + _, _, err := buildRequest(op, map[string]any{"projectId": []any{1}, "todolistId": "2"}) + assert.ErrorContains(t, err, `path parameter "projectId"`) + }) + + t.Run("stray param on a body-less operation", func(t *testing.T) { + _, _, err := buildRequest(op, map[string]any{"projectId": "1", "todolistId": "2", "content": "x"}) + assert.ErrorContains(t, err, `unknown parameter "content"`) + }) +} + +func TestBuildRequestRailsArrayParams(t *testing.T) { + op := &catalog.Operation{ + Action: "search", + Method: "GET", + Path: "/search.json", + Params: []catalog.Param{ + {Name: "q", In: "query", Required: true, Schema: map[string]any{"type": "string"}}, + {Name: "type_names[]", In: "query", Schema: map[string]any{"type": "array"}}, + }, + } + + t.Run("repeats the bracketed name per element", func(t *testing.T) { + path, _, err := buildRequest(op, map[string]any{ + "q": "milk", + "type_names[]": []any{"Todo", "Message"}, + }) + require.NoError(t, err) + assert.Equal(t, "/search.json?q=milk&type_names%5B%5D=Todo&type_names%5B%5D=Message", path) + }) + + t.Run("accepts a single scalar too", func(t *testing.T) { + path, _, err := buildRequest(op, map[string]any{"q": "milk", "type_names[]": "Todo"}) + require.NoError(t, err) + assert.Equal(t, "/search.json?q=milk&type_names%5B%5D=Todo", path) + }) + + t.Run("rejects non-scalar elements", func(t *testing.T) { + _, _, err := buildRequest(op, map[string]any{"q": "milk", "type_names[]": []any{map[string]any{}}}) + assert.ErrorContains(t, err, `query parameter "type_names[]"`) + }) +} + +func TestBuildRequestBody(t *testing.T) { + op := &catalog.Operation{ + Action: "create_todo", + Method: "POST", + Path: "/buckets/{projectId}/todolists/{todolistId}/todos.json", + BodyRequired: true, + Params: []catalog.Param{ + {Name: "projectId", In: "path", Required: true, Schema: map[string]any{"type": "string"}}, + {Name: "todolistId", In: "path", Required: true, Schema: map[string]any{"type": "string"}}, + }, + Body: map[string]any{ + "type": "object", + "properties": map[string]any{ + "content": map[string]any{"type": "string"}, + "description": map[string]any{"type": "string"}, + }, + }, + } + + t.Run("gathers remaining params into the body", func(t *testing.T) { + path, body, err := buildRequest(op, map[string]any{ + "projectId": "1", + "todolistId": "2", + "content": "Buy milk", + }) + require.NoError(t, err) + assert.Equal(t, "/buckets/1/todolists/2/todos.json", path) + assert.Equal(t, map[string]any{"content": "Buy milk"}, body) + }) + + t.Run("rejects properties the body schema does not declare", func(t *testing.T) { + _, _, err := buildRequest(op, map[string]any{ + "projectId": "1", "todolistId": "2", "contnet": "typo", + }) + assert.ErrorContains(t, err, `unknown parameter "contnet"`) + }) + + t.Run("required body is sent even when empty", func(t *testing.T) { + _, body, err := buildRequest(op, map[string]any{"projectId": "1", "todolistId": "2"}) + require.NoError(t, err) + assert.Equal(t, map[string]any{}, body) + }) + + t.Run("optional body is omitted when empty", func(t *testing.T) { + optional := *op + optional.BodyRequired = false + _, body, err := buildRequest(&optional, map[string]any{"projectId": "1", "todolistId": "2"}) + require.NoError(t, err) + assert.Nil(t, body) + }) +} + +func TestScalarString(t *testing.T) { + for _, tc := range []struct { + in any + want string + }{ + {"abc", "abc"}, + {true, "true"}, + {float64(42), "42"}, + {float64(4.5), "4.5"}, + } { + got, err := scalarString(tc.in) + require.NoError(t, err) + assert.Equal(t, tc.want, got) + } + + _, err := scalarString(map[string]any{}) + assert.ErrorContains(t, err, "must be a string, number, or boolean") +} + +func TestNextPage(t *testing.T) { + link := func(value string) http.Header { + h := http.Header{} + h.Set("Link", value) + return h + } + + assert.Equal(t, "4", + nextPage(link(`; rel="next"`))) + assert.Equal(t, "2", + nextPage(link(`; rel="prev", ; rel="next"`))) + assert.Empty(t, nextPage(link(`; rel="prev"`)), "no next link") + assert.Empty(t, nextPage(link(`; rel="next"`)), "next link without page") + assert.Empty(t, nextPage(http.Header{}), "no Link header") +} diff --git a/internal/mcpserver/domains.go b/internal/mcpserver/domains.go new file mode 100644 index 00000000..a65d461b --- /dev/null +++ b/internal/mcpserver/domains.go @@ -0,0 +1,97 @@ +package mcpserver + +import "github.com/basecamp/mcp/catalog" + +// DomainSpecs curates which slice of the basecamp-sdk surface each domain +// gateway tool exposes, in tool display order. Tags are basecamp-sdk's +// OpenAPI tags (each operation carries exactly one); a spec may merge +// several tags into one tool. This mapping is the only hand-maintained part +// of the catalog — everything else derives from the SDK model via the +// toolkit. +// +// The grouping mirrors basecamp-mcp-server's domain curation (projects, +// todos, cards, messages, campfires, schedules, files, people, account) +// where the SDK's tags allow, and follows the tags where they don't: the +// server's checkins domain lives inside the SDK's Automation tag alongside +// templates, webhooks, lineup, dock tools, and search, so it is served as +// the automation domain; the server's admin grab-bag lands across reports, +// everything, and automation. Every tag is claimed — Catalog.Unmapped is +// pinned empty by tests, so an SDK tag nobody has decided about fails the +// build. +var DomainSpecs = []catalog.DomainSpec{ + { + Key: "projects", + Tags: []string{"Projects"}, + Blurb: "Basecamp projects: list, get, create, update, archive, unarchive, and trash.", + }, + { + Key: "todos", + Tags: []string{"Todos"}, + Blurb: "Todos, todolists, todolist groups, and todosets — plus each todolist's hill chart.", + }, + { + Key: "cards", + Tags: []string{"Card Tables"}, + Blurb: "Card tables (kanban): cards, columns, steps, and wormholes, with moves, repositioning, and on-hold state.", + }, + { + Key: "messages", + Tags: []string{"Messages"}, + Blurb: "Message boards: messages, comments, message types (categories), and pinning.", + }, + { + Key: "campfires", + Tags: []string{"Campfire"}, + Blurb: "Campfire chat: rooms, chat lines, uploads, and chatbots.", + }, + { + Key: "boosts", + Tags: []string{"Boosts"}, + Blurb: "Boosts (emoji reactions) on recordings and their events: list, get, create, and delete.", + }, + { + Key: "schedules", + Tags: []string{"Schedule", "Calendars"}, + Blurb: "Schedules and schedule entries (calendar events), timesheets and time tracking, and per-account calendars.", + }, + { + Key: "files", + Tags: []string{"Files", "Folders"}, + Blurb: "Docs & Files: vaults (folders), documents, uploads, attachments, cloud file links, Google documents, and home-screen project folders.", + }, + { + Key: "people", + Tags: []string{"People"}, + Blurb: "People and access: profiles, pingable people, project access, out-of-office, preferences, and notification subscriptions.", + }, + { + Key: "automation", + Tags: []string{"Automation"}, + Blurb: "Automatic check-ins (questionnaires, questions, answers, reminders), project templates, webhooks, lineup markers, dock tools, recording lifecycle (archive/trash), change events, and search.", + }, + { + Key: "reports", + Tags: []string{"Reports"}, + Blurb: "Reports and timelines: progress, assigned and overdue todos, upcoming schedule, per-person progress, and project timelines.", + }, + { + Key: "everything", + Tags: []string{"Everything"}, + Blurb: "Account-wide feeds: every checkin, comment, file, forward, and message, and cards and todos filtered by state (open, completed, overdue, unassigned, no due date, not now).", + }, + { + Key: "clientside", + Tags: []string{"ClientFeatures"}, + Blurb: "The Clientside: client approvals, correspondences, replies, and client visibility of recordings.", + }, + { + Key: "forwards", + Tags: []string{"Forwards"}, + Blurb: "Email forwards: project inboxes, forwarded emails, and their replies.", + }, + { + Key: "account", + Tags: []string{"Account", "Gauges", "MyAssignments", "MyNotes", "MyNotifications", "Bookmarks", "Drafts"}, + Blurb: "Account info and your personal surface: gauges and needles, my assignments and priorities, notifications and bubble-ups, bookmarks, personal note, and drafts.", + }, +} diff --git a/internal/mcpserver/model/PROVENANCE.json b/internal/mcpserver/model/PROVENANCE.json new file mode 100644 index 00000000..64be29e2 --- /dev/null +++ b/internal/mcpserver/model/PROVENANCE.json @@ -0,0 +1,8 @@ +{ + "source": "github.com/basecamp/basecamp-sdk", + "commit": "e47f90a60376743eb8f4bbc372f295a065adccbe", + "ref": "go/v0.14.0", + "files": ["behavior-model.json", "openapi.json"], + "synced_by": "scripts/sync-mcp-model.sh", + "patches": "tags assigned to operations the export leaves untagged (PATCHED_TAGS); binary-upload operations dropped (EXCLUDED_OPERATIONS) — see the sync script" +} diff --git a/internal/mcpserver/model/behavior-model.json b/internal/mcpserver/model/behavior-model.json new file mode 100644 index 00000000..047211c0 --- /dev/null +++ b/internal/mcpserver/model/behavior-model.json @@ -0,0 +1,3252 @@ +{ + "$schema": "https://basecamp.com/schemas/behavior-model.json", + "version": "1.0.0", + "generated": true, + "operations": { + "ArchiveProject": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ArchiveRecording": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CompleteTodo": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateAnswer": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateBookmark": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateCampfireLine": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateCard": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateCardColumn": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateCardStep": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateChatbot": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateCloudFile": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateComment": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateDocument": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateEventBoost": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateFolder": { + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateGaugeNeedle": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateGoogleDocument": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateLineupMarker": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateMessage": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateMessageType": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateProject": { + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateProjectFromTemplate": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateQuestion": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateRecordingBoost": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateScheduleEntry": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTemplate": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTimesheetEntry": { + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTodo": { + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTodolist": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTodolistGroup": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTodosetTodo": { + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateTool": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateUpload": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateUploadVersion": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateVault": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateWebhook": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "CreateWormhole": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteBookmark": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteBoost": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteCampfireLine": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteChatbot": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteFolder": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteLineupMarker": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteMessageType": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteTemplate": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteTool": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteWebhook": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeleteWormhole": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DeprioritizeAssignment": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DestroyGaugeNeedle": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DestroyTimesheetEntry": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DisableCardColumnOnHold": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DisableOutOfOffice": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "DisableTool": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "EnableCardColumnOnHold": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "EnableOutOfOffice": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "EnableTool": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetAccount": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetAnswer": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetAnswersByPerson": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetAssignedTodos": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetBookmark": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetBoost": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetBubbleUps": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCalendar": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCampfire": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCampfireLine": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCard": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCardColumn": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCardStep": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCardTable": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetChatbot": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetClientApproval": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetClientCorrespondence": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetClientReply": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetCloudFile": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetComment": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetDocument": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingCheckins": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingComments": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingCompletedCards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingCompletedTodos": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingFiles": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingForwards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingMessages": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingNoDueDateCards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingNoDueDateTodos": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingNotNowCards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOpenCards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOpenTodos": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOverdueCards": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOverdueTodos": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingUnassignedCards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingUnassignedTodos": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 5 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetFolder": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetForward": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetForwardReply": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetGaugeNeedle": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetGoogleDocument": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetHillChart": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetInbox": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMessage": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMessageBoard": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMessageType": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyAssignments": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyCompletedAssignments": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyDueAssignments": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyNote": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyNotifications": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyPreferences": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetMyProfile": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetOutOfOffice": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetOverdueTodos": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetPerson": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetPersonProgress": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetProgressReport": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetProject": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetProjectConstruction": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetProjectTimeline": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetProjectTimesheet": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetQuestion": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetQuestionReminders": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetQuestionnaire": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetRecordingTimesheet": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetSchedule": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetScheduleEntry": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetScheduleEntryOccurrence": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetSearchMetadata": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetSubscription": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTemplate": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTimesheetEntry": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTimesheetReport": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTodo": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTodolistOrGroup": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTodoset": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetTool": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetUpcomingSchedule": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetUpload": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetVault": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetWebhook": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListAnswers": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListAssignablePeople": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListCampfireLines": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListCampfireUploads": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListCampfires": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListCards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListChatbots": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListClientApprovals": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListClientCorrespondences": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListClientReplies": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListComments": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListDocuments": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListEventBoosts": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListEvents": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListFolders": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListForwardReplies": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListForwards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListGaugeNeedles": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListGauges": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListLineupMarkers": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListMessageTypes": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListMessages": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListMyBookmarks": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListMyDrafts": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListPeople": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListPingablePeople": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListProjectPeople": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListProjects": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListQuestionAnswerers": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListQuestions": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListRecordingBoosts": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListRecordings": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListScheduleEntries": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListTemplates": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListTodolistGroups": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListTodolists": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListTodos": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListUploadVersions": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListUploads": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListVaults": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ListWebhooks": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "MarkAsRead": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "MoveCard": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "MoveCardColumn": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "PauseQuestion": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "PinMessage": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "PrioritizeAssignment": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "RemoveAccountLogo": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ReorderUpNext": { + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ReplaceDocument": { + "idempotent": true, + "write": { + "mode": "replace", + "clearsOmitted": true + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ReplaceScheduleEntry": { + "idempotent": true, + "write": { + "mode": "replace", + "clearsOmitted": true, + "preservedOnOmission": [ + "participant_ids", + "url", + "highlighted" + ] + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ReplaceTodo": { + "idempotent": true, + "write": { + "mode": "replace", + "clearsOmitted": true + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "RepositionCardStep": { + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "RepositionTodo": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "RepositionTodolist": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "RepositionTodolistGroup": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "RepositionTool": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ResumeQuestion": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "Search": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "SetCardColumnColor": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "SetCardStepCompletion": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "SetClientVisibility": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "Subscribe": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "SubscribeToCardColumn": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "ToggleGauge": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "TrashProject": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "TrashRecording": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UnarchiveProject": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UnarchiveRecording": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UncompleteTodo": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UnpinMessage": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "Unsubscribe": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UnsubscribeFromCardColumn": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateAccountName": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateAnswer": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateCalendar": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateCampfireLine": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateCard": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateCardColumn": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateCardStep": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateChatbot": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateCloudFile": { + "idempotent": true, + "write": { + "mode": "replace", + "clearsOmitted": true + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateComment": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateFolder": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateGaugeNeedle": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateGoogleDocument": { + "idempotent": true, + "write": { + "mode": "replace", + "clearsOmitted": true + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateHillChartSettings": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateLineupMarker": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateMessage": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateMessageType": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateMyNote": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateMyPreferences": { + "idempotent": true, + "retry": { + "max": 2, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateMyProfile": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateProject": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateProjectAccess": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateQuestion": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateQuestionNotificationSettings": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateScheduleSettings": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateSubscription": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateTemplate": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateTimesheetEntry": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateTodolistOrGroup": { + "idempotent": true, + "write": { + "mode": "replace", + "clearsOmitted": true + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateTool": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateUpload": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateVault": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateWebhook": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "UpdateWormhole": { + "idempotent": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + } + }, + "redaction": { + "CreatePersonRequest": [ + "$.name", + "$.email_address", + "$.title", + "$.company_name" + ], + "MyAssignmentAssignee": [ + "$.name", + "$.avatar_url" + ], + "OutOfOfficePerson": [ + "$.name", + "$.avatar_url" + ], + "Person": [ + "$.name", + "$.email_address", + "$.title", + "$.bio", + "$.tagline", + "$.location", + "$.avatar_url" + ], + "PersonCompany": [ + "$.name" + ], + "UpcomingSchedulePerson": [ + "$.name", + "$.avatar_url" + ], + "UpdateMyProfileInput": [ + "$.name", + "$.email_address", + "$.title", + "$.bio", + "$.location" + ] + }, + "sensitiveTypes": [ + "AvatarUrl", + "CompanyName", + "EmailAddress", + "PersonBio", + "PersonLocation", + "PersonName", + "PersonTitle" + ] +} diff --git a/internal/mcpserver/model/openapi.json b/internal/mcpserver/model/openapi.json new file mode 100644 index 00000000..e05f59e4 --- /dev/null +++ b/internal/mcpserver/model/openapi.json @@ -0,0 +1,35996 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Basecamp", + "version": "2026-08-05", + "description": "Basecamp API", + "contact": { + "name": "Basecamp", + "url": "https://github.com/basecamp/basecamp-sdk" + }, + "license": { + "name": "MIT", + "url": "https://github.com/basecamp/basecamp-sdk/blob/main/LICENSE" + } + }, + "paths": { + "/{accountId}/account.json": { + "get": { + "description": "Get the account for the current access token", + "operationId": "GetAccount", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetAccount 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAccountResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Account" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/account/logo.json": { + "delete": { + "description": "Remove the account logo. Only administrators and account owners can use this endpoint.", + "operationId": "RemoveAccountLogo", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "RemoveAccountLogo 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Account" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/account/name.json": { + "put": { + "description": "Rename the current account. Only account owners can use this endpoint.", + "operationId": "UpdateAccountName", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAccountNameRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateAccountName 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAccountNameResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Account" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/boosts/{boostId}": { + "delete": { + "description": "Delete a boost", + "operationId": "DeleteBoost", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "boostId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteBoost 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boosts" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a single boost", + "operationId": "GetBoost", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "boostId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetBoost 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetBoostResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boosts" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/card_tables/columns/{columnId}/color.json": { + "put": { + "description": "Set the color of a column", + "operationId": "SetCardColumnColor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetCardColumnColorRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "SetCardColumnColor 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetCardColumnColorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/card_tables/columns/{columnId}/on_hold.json": { + "delete": { + "description": "Disable on-hold section in a column", + "operationId": "DisableCardColumnOnHold", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "DisableCardColumnOnHold 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DisableCardColumnOnHoldResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Enable on-hold section in a column", + "operationId": "EnableCardColumnOnHold", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "EnableCardColumnOnHold 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnableCardColumnOnHoldResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/card_tables/wormholes/{wormholeId}": { + "delete": { + "description": "Delete a wormhole", + "operationId": "DeleteWormhole", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "wormholeId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteWormhole 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update a wormhole's destination column", + "operationId": "UpdateWormhole", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWormholeRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "wormholeId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateWormhole 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWormholeResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/card_tables/{cardTableId}/wormholes.json": { + "post": { + "description": "Create a wormhole linking this card table to a column on another card table.\n\nA wormhole is the only mechanism for moving a card to a different project: its\nid is a valid `column_id` for MoveCard, teleporting the card across projects.\n`destinationRecordingId` is the id of a column on another accessible card table.", + "operationId": "CreateWormhole", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWormholeRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "cardTableId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateWormhole 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWormholeResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/categories.json": { + "get": { + "description": "List message types in a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListMessageTypes", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListMessageTypes 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListMessageTypesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new message type in a project", + "operationId": "CreateMessageType", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMessageTypeRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateMessageType 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMessageTypeResponseContent" + } + } + } + }, + "400": { + "description": "BareFieldBadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BareFieldBadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/categories/{typeId}": { + "delete": { + "description": "Delete a message type", + "operationId": "DeleteMessageType", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "typeId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteMessageType 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a single message type by id", + "operationId": "GetMessageType", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "typeId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMessageType 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMessageTypeResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing message type", + "operationId": "UpdateMessageType", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMessageTypeRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "typeId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateMessageType 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMessageTypeResponseContent" + } + } + } + }, + "400": { + "description": "BareFieldBadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BareFieldBadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/chats/{campfireId}/integrations.json": { + "get": { + "description": "List all chatbots for a campfire\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListChatbots", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListChatbots 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListChatbotsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new chatbot for a campfire", + "operationId": "CreateChatbot", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateChatbotRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateChatbot 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateChatbotResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/chats/{campfireId}/integrations/{chatbotId}": { + "delete": { + "description": "Delete a chatbot", + "operationId": "DeleteChatbot", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "chatbotId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteChatbot 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a chatbot by ID", + "operationId": "GetChatbot", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "chatbotId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetChatbot 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetChatbotResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing chatbot", + "operationId": "UpdateChatbot", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateChatbotRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "chatbotId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateChatbot 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateChatbotResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/client/approvals.json": { + "get": { + "description": "List all client approvals in a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListClientApprovals", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListClientApprovals 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListClientApprovalsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/client/correspondences.json": { + "get": { + "description": "List all client correspondences in a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListClientCorrespondences", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListClientCorrespondences 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListClientCorrespondencesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/client/recordings/{recordingId}/replies.json": { + "get": { + "description": "List all client replies for a recording (correspondence or approval)\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListClientReplies", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListClientReplies 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListClientRepliesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/client/recordings/{recordingId}/replies/{replyId}": { + "get": { + "description": "Get a single client reply by id", + "operationId": "GetClientReply", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "replyId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetClientReply 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetClientReplyResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/dock/tools.json": { + "post": { + "description": "Create a tool in a project dock", + "operationId": "CreateTool", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateToolRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTool 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateToolResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/todosets/{todosetId}/todos.json": { + "post": { + "description": "Create a to-do directly under a project's to-do set, outside any to-do list.\nThis form exists only project-scoped (no account-scoped variant); parameters\nand response match the to-do-list create. Find a project's to-do set id via\nGetTodoset.", + "operationId": "CreateTodosetTodo", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodosetTodoRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "todosetId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTodosetTodo 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodosetTodoResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/vaults/{vaultId}/cloud_files.json": { + "post": { + "description": "Create a new cloud file in a vault.\n`url` is validated against the selected service's patterns, so it must be a\nreal link for that service; use service \"other\" for anything that matches no\nrecognized service. Omitting `title` is allowed and reads back as\n\"Untitled\".", + "operationId": "CreateCloudFile", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCloudFileRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateCloudFile 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCloudFileResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/vaults/{vaultId}/google_documents.json": { + "post": { + "description": "Create a new Google document in a vault.\nAn unrecognized `document_type` is rejected before validation with the\nfield-keyed 422 {\"errors\": {\"document_type\": [\"is not a valid document\ntype\"]}} \u2014 the enum would otherwise raise rather than add an error.\nOmitting `title` is allowed and reads back as \"Untitled\".", + "operationId": "CreateGoogleDocument", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGoogleDocumentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateGoogleDocument 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGoogleDocumentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/buckets/{bucketId}/webhooks.json": { + "get": { + "description": "List all webhooks for a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListWebhooks", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListWebhooks 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListWebhooksResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new webhook for a project", + "operationId": "CreateWebhook", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWebhookRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucketId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateWebhook 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWebhookResponseContent" + } + } + } + }, + "400": { + "description": "BareFieldBadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BareFieldBadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "507": { + "description": "WebhookLimitError 507 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookLimitErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/calendars/{calendarId}": { + "get": { + "description": "Get a calendar by its bucket id. A Calendar is a top-level BC5 bucketable\n(distinct from a project) exposing display metadata and a link to its\nunderlying schedule resource. Shipped scope is show + update only.", + "operationId": "GetCalendar", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "calendarId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCalendar 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCalendarResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Calendars" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update a calendar's display color. An unknown color returns 422 with a JSON\nerrors payload keyed by field ({\"errors\": {\"color\": [\"is not a valid\ncolor\"]}}) \u2014 the controller rejects invalid enum values up front.", + "operationId": "UpdateCalendar", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCalendarRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "calendarId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateCalendar 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCalendarResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Calendars" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/cards/{cardId}": { + "get": { + "description": "Get a card by ID", + "operationId": "GetCard", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCard 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCardResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing card", + "operationId": "UpdateCard", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCardRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateCard 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCardResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/cards/{cardId}/moves.json": { + "post": { + "description": "Move a card to a different column", + "operationId": "MoveCard", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveCardRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "MoveCard 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/cards/{cardId}/positions.json": { + "post": { + "description": "Reposition a step within a card", + "operationId": "RepositionCardStep", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositionCardStepRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "RepositionCardStep 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/cards/{cardId}/steps.json": { + "post": { + "description": "Create a step on a card", + "operationId": "CreateCardStep", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCardStepRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateCardStep 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCardStepResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/columns/{columnId}": { + "get": { + "description": "Get a card column by ID", + "operationId": "GetCardColumn", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCardColumn 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCardColumnResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing column", + "operationId": "UpdateCardColumn", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCardColumnRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateCardColumn 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCardColumnResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/lists/{columnId}/cards.json": { + "get": { + "description": "List cards in a column\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a card in a column", + "operationId": "CreateCard", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCardRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateCard 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCardResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/lists/{columnId}/subscription.json": { + "delete": { + "description": "Unsubscribe from a card column (stop watching for changes)", + "operationId": "UnsubscribeFromCardColumn", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UnsubscribeFromCardColumn 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Card Tables" + ] + }, + "post": { + "description": "Subscribe to a card column (watch for changes)", + "operationId": "SubscribeToCardColumn", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "columnId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "SubscribeToCardColumn 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Card Tables" + ] + } + }, + "/{accountId}/card_tables/steps/{stepId}": { + "get": { + "description": "Get a step by ID", + "operationId": "GetCardStep", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "stepId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCardStep 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCardStepResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing step", + "operationId": "UpdateCardStep", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCardStepRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "stepId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateCardStep 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCardStepResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/steps/{stepId}/completions.json": { + "put": { + "description": "Set card step completion status (PUT with completion: \"on\" to complete, \"\" to uncomplete)", + "operationId": "SetCardStepCompletion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetCardStepCompletionRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "stepId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "SetCardStepCompletion 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetCardStepCompletionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/{cardTableId}": { + "get": { + "description": "Get a card table by ID", + "operationId": "GetCardTable", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardTableId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCardTable 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCardTableResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/{cardTableId}/columns.json": { + "post": { + "description": "Create a column in a card table", + "operationId": "CreateCardColumn", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCardColumnRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardTableId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateCardColumn 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCardColumnResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/card_tables/{cardTableId}/moves.json": { + "post": { + "description": "Move a column within a card table", + "operationId": "MoveCardColumn", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MoveCardColumnRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cardTableId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "MoveCardColumn 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Card Tables" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cards/completed.json": { + "get": { + "description": "Completed cards across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingCompletedCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingCompletedCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCompletedCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cards/no_due_date.json": { + "get": { + "description": "Open cards with no due date across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingNoDueDateCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingNoDueDateCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingNoDueDateCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cards/not_now.json": { + "get": { + "description": "Cards parked in a project's \"Not now\" column across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingNotNowCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingNotNowCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingNotNowCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cards/open.json": { + "get": { + "description": "Incomplete cards in active columns across all accessible projects, grouped by project (paginated).\nEach bucket entry carries the matching cards and their steps.", + "operationId": "GetEverythingOpenCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingOpenCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOpenCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cards/overdue.json": { + "get": { + "description": "Get every overdue card across all accessible projects, oldest-due-date-first.\nA complete, unpaginated array; each item embeds its `bucket`.", + "operationId": "GetEverythingOverdueCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + } + ], + "responses": { + "200": { + "description": "GetEverythingOverdueCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOverdueCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cards/unassigned.json": { + "get": { + "description": "Open, unassigned cards across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingUnassignedCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingUnassignedCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingUnassignedCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/chats.json": { + "get": { + "description": "List all campfires across the account\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListCampfires", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListCampfires 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCampfiresResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/chats/{campfireId}": { + "get": { + "description": "Get a campfire by ID", + "operationId": "GetCampfire", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCampfire 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCampfireResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/chats/{campfireId}/lines.json": { + "get": { + "description": "List all lines (messages) in a campfire\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListCampfireLines", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListCampfireLines 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCampfireLinesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new line (message) in a campfire", + "operationId": "CreateCampfireLine", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCampfireLineRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateCampfireLine 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCampfireLineResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/chats/{campfireId}/lines/{lineId}": { + "delete": { + "description": "Delete a campfire line; allowed for the line's creator or an admin.\nThe API responds 403 Forbidden otherwise.", + "operationId": "DeleteCampfireLine", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "lineId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteCampfireLine 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a campfire line by ID", + "operationId": "GetCampfireLine", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "lineId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCampfireLine 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCampfireLineResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing campfire line; the content is always treated as rich text (HTML).\nThe server coerces every edited line to rich text and ignores any content\ntype hint. Only the line's creator may edit it, and only text and\nrich-text lines are editable.", + "operationId": "UpdateCampfireLine", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCampfireLineRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "lineId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UpdateCampfireLine 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/chats/{campfireId}/uploads.json": { + "get": { + "description": "List uploaded files in a campfire\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListCampfireUploads", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "campfireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListCampfireUploads 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCampfireUploadsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Campfire" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/checkins.json": { + "get": { + "description": "Get every automatic check-in answer across all accessible projects, newest-first.\nPaginated; each item embeds its `bucket`.", + "operationId": "GetEverythingCheckins", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingCheckins 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCheckinsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/circles/people.json": { + "get": { + "description": "List all account users who can be pinged\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListPingablePeople", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListPingablePeople 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListPingablePeopleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/client/approvals/{approvalId}": { + "get": { + "description": "Get a single client approval by id", + "operationId": "GetClientApproval", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "approvalId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetClientApproval 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetClientApprovalResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/client/correspondences/{correspondenceId}": { + "get": { + "description": "Get a single client correspondence by id", + "operationId": "GetClientCorrespondence", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "correspondenceId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetClientCorrespondence 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetClientCorrespondenceResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/cloud_files/{cloudFileId}": { + "get": { + "description": "Get a single cloud file by id", + "operationId": "GetCloudFile", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cloudFileId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetCloudFile 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCloudFileResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace a cloud file with a new complete representation.\nBC3 builds a brand-new CloudFile from the permitted params and swaps the\nrecordable wholesale, so the request body is the full writable state:\nomitting `title` clears it (and the cloud file then reads back as\n\"Untitled\"), and omitting `description` clears it. `url` and `service` carry\npresence/format validations, so they are required here rather than\nclearable \u2014 a request without them is a 422, not a silent wipe.\nUpdating a drafted cloud file also publishes it.\nSubscribers are the exception to omission-clears: a drafted cloud file keeps\nits current subscribers when the request addresses neither `subscriptions`\nnor `notify`. The creator is always on the list.\nThe legacy bucket-scoped path PUT /buckets/{bucketId}/cloud_files/{id}.json\nis also accepted by BC3; this flat spelling is the documented one.", + "operationId": "UpdateCloudFile", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCloudFileRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "cloudFileId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateCloudFile 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCloudFileResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "x-basecamp-write-semantics": { + "mode": "replace", + "clearsOmitted": true + } + } + }, + "/{accountId}/comments.json": { + "get": { + "description": "Get every comment across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket`.", + "operationId": "GetEverythingComments", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingComments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCommentsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/comments/{commentId}": { + "get": { + "description": "Get a single comment by id", + "operationId": "GetComment", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "commentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetComment 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCommentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing comment", + "operationId": "UpdateComment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCommentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "commentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateComment 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCommentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/dock/tools/{toolId}": { + "delete": { + "description": "Delete a tool (trash it)", + "operationId": "DeleteTool", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "toolId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteTool 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a dock tool by id", + "operationId": "GetTool", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "toolId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetTool 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetToolResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update (rename) an existing tool", + "operationId": "UpdateTool", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateToolRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "toolId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateTool 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateToolResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/documents/{documentId}": { + "get": { + "description": "Get a single document by id", + "operationId": "GetDocument", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "documentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetDocument 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetDocumentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace a document with a new complete representation.\nThe request body is the document's full writable state: any writable field\nomitted from the request is cleared server-side. Omitting content clears it;\nomitting title clears it too, and the document then reads back as\n\"Untitled\" (Document#title falls back when blank).\nNeither field is required. BC3 builds a brand-new Document from the\npermitted params and swaps the recordable wholesale, and neither attribute\ncarries a presence validation \u2014 so an omission is a 200 that clears, not a\n422. What BC3 does require is the wrapping document object, which Rails\nsynthesizes from a flat body, so a request naming neither field is a 400.\nPublishing a draft (status: \"active\") is not modeled: the SDK sends only\ntitle and content, and BC3 rejects a status-only update for the same\nreason it 400s an empty body.\nSubscribers are the one exception to omission-clears. A drafted document\nkeeps its current subscribers when the request addresses neither\nsubscriptions nor notify, so a full-representation PUT that mentions\nneither is safe on a draft.\nTo set some fields while preserving the rest, use the SDK's merge-safe\nupdate or edit methods, which GET the current document and PUT the full\nrepresentation back. Those read-modify-write helpers are not atomic:\na concurrent write between the GET and PUT is overwritten (last write\nwins for the whole representation; the window is one round-trip).", + "operationId": "ReplaceDocument", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceDocumentRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "documentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ReplaceDocument 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceDocumentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "x-basecamp-write-semantics": { + "mode": "replace", + "clearsOmitted": true + } + } + }, + "/{accountId}/files.json": { + "get": { + "description": "Get every file recording across all accessible projects, newest-first (paginated).\nHeterogeneous: uploads and Basecamp documents carry their\nstandard recording shapes, while rich-text attachments are wrapped in a\nrecording envelope plus an `attachable_sgid` and blob metadata. Modeled as\nan optional-field superset (EverythingFile) so one element type decodes any\nvariant.", + "operationId": "GetEverythingFiles", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "kind", + "in": "query", + "description": "Filter by file kind: all (default), images, pdfs, documents, or videos.", + "schema": { + "type": "string", + "description": "Filter by file kind: all (default), images, pdfs, documents, or videos." + } + }, + { + "name": "people_ids[]", + "in": "query", + "description": "Restrict to files created by the given people (repeatable).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to files created by the given people (repeatable)." + }, + "explode": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingFiles 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingFilesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/forwards.json": { + "get": { + "description": "Get every inbox forward across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket`.", + "operationId": "GetEverythingForwards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingForwards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingForwardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/gauge_needles/{needleId}": { + "delete": { + "description": "Destroy a gauge needle", + "operationId": "DestroyGaugeNeedle", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "needleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DestroyGaugeNeedle 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a gauge needle by ID", + "operationId": "GetGaugeNeedle", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "needleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetGaugeNeedle 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetGaugeNeedleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update a gauge needle's description. Position and color are immutable.", + "operationId": "UpdateGaugeNeedle", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateGaugeNeedleRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "needleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateGaugeNeedle 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateGaugeNeedleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/google_documents/{googleDocumentId}": { + "get": { + "description": "Get a single Google document by id", + "operationId": "GetGoogleDocument", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "googleDocumentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetGoogleDocument 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetGoogleDocumentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace a Google document with a new complete representation.\nBC3 builds a brand-new GoogleDocument from the permitted params and swaps\nthe recordable wholesale, so the request body is the full writable state:\nomitting `title` clears it (and the document then reads back as \"Untitled\"),\nand omitting `description` clears it. `url` and `document_type` are required\nhere \u2014 `document_type` because an absent or unrecognized value is a 422, and\n`url` because it carries a presence validation.\nSubscribers are the exception to omission-clears: a drafted document keeps\nits current subscribers when the request addresses neither `subscriptions`\nnor `notify`. The creator is always on the list.\nThe legacy bucket-scoped path\nPUT /buckets/{bucketId}/google_documents/{id}.json is also accepted by BC3;\nthis flat spelling is the documented one.", + "operationId": "UpdateGoogleDocument", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateGoogleDocumentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "googleDocumentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateGoogleDocument 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateGoogleDocumentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "x-basecamp-write-semantics": { + "mode": "replace", + "clearsOmitted": true + } + } + }, + "/{accountId}/inbox_forwards/{forwardId}": { + "get": { + "description": "Get a forward by ID", + "operationId": "GetForward", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "forwardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetForward 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetForwardResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Forwards" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/inbox_forwards/{forwardId}/replies.json": { + "get": { + "description": "List all replies to a forward\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListForwardReplies", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "forwardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListForwardReplies 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListForwardRepliesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Forwards" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/inbox_forwards/{forwardId}/replies/{replyId}": { + "get": { + "description": "Get a forward reply by ID", + "operationId": "GetForwardReply", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "forwardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "replyId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetForwardReply 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetForwardReplyResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Forwards" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/inboxes/{inboxId}": { + "get": { + "description": "Get an inbox by ID", + "operationId": "GetInbox", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "inboxId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetInbox 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetInboxResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Forwards" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/inboxes/{inboxId}/inbox_forwards.json": { + "get": { + "description": "List all forwards in an inbox\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListForwards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "inboxId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListForwards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListForwardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Forwards" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/lineup/markers.json": { + "get": { + "description": "List all lineup markers for the account", + "operationId": "ListLineupMarkers", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListLineupMarkers 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListLineupMarkersResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new lineup marker", + "operationId": "CreateLineupMarker", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLineupMarkerRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateLineupMarker 201 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "BareFieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BareFieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/lineup/markers/{markerId}": { + "delete": { + "description": "Delete a lineup marker", + "operationId": "DeleteLineupMarker", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "markerId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteLineupMarker 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing lineup marker", + "operationId": "UpdateLineupMarker", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLineupMarkerRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "markerId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateLineupMarker 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "BareFieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BareFieldValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/message_boards/{boardId}": { + "get": { + "description": "Get a message board", + "operationId": "GetMessageBoard", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "boardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMessageBoard 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMessageBoardResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/message_boards/{boardId}/messages.json": { + "get": { + "description": "List messages on a message board\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListMessages", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "boardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListMessages 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListMessagesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new message on a message board", + "operationId": "CreateMessage", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMessageRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "boardId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateMessage 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMessageResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/messages.json": { + "get": { + "description": "Get every message across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket` for project context.", + "operationId": "GetEverythingMessages", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingMessages 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingMessagesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/messages/{messageId}": { + "get": { + "description": "Get a single message by id", + "operationId": "GetMessage", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "messageId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMessage 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMessageResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing message", + "operationId": "UpdateMessage", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMessageRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "messageId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateMessage 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMessageResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/assignments.json": { + "get": { + "description": "Get the current user's active assignments grouped into priorities and non_priorities.\nCard table steps are normalized to their parent card with steps as children.\nThis endpoint is not paginated.", + "operationId": "GetMyAssignments", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMyAssignments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyAssignmentsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyAssignments" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/assignments/completed.json": { + "get": { + "description": "Get the current user's completed assignments.\nArchived and trashed recordings are excluded. This endpoint is not paginated.", + "operationId": "GetMyCompletedAssignments", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMyCompletedAssignments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyCompletedAssignmentsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyAssignments" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/assignments/due.json": { + "get": { + "description": "Get the current user's assignments filtered by due date scope.\nDefaults to overdue when no scope is provided. This endpoint is not paginated.", + "operationId": "GetMyDueAssignments", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "scope", + "in": "query", + "description": "Filter by due date range: overdue, due_today, due_tomorrow,\ndue_later_this_week, due_next_week, due_later", + "schema": { + "type": "string", + "description": "Filter by due date range: overdue, due_today, due_tomorrow,\ndue_later_this_week, due_next_week, due_later" + } + } + ], + "responses": { + "200": { + "description": "GetMyDueAssignments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyDueAssignmentsResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyAssignments" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/bookmarks.json": { + "get": { + "description": "List the current user's bookmarks, most recently bookmarked first (paginated).\nA bookmark is a personal link between the current user and a single recording,\nvisible only to its creator; each entry wraps the shared recording projection.", + "operationId": "ListMyBookmarks", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListMyBookmarks 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListMyBookmarksResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Bookmarks" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/drafts.json": { + "get": { + "description": "List the current user's drafts across their active projects, most recently\nupdated first (paginated, capped at 250 like /my/assignments). Five draft\nkinds are returned: messages, documents, uploads, client approvals, and\nclient correspondences. Drafts under archived or trashed projects are\nexcluded.", + "operationId": "ListMyDrafts", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListMyDrafts 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListMyDraftsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Drafts" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/notes.json": { + "get": { + "description": "Get the authenticated user's note \u2014 a per-person notebook singleton at\n/my/notes.json. If the user has not yet written anything, the shape is the\nsame with empty content and null id/created_at/updated_at; the record is\ncreated on first update.", + "operationId": "GetMyNote", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMyNote 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyNoteResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyNotes" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace the note's content, recording a new revision server-side.\nThe first update also creates the underlying notebook if the user did not\nhave one yet. Returns the updated note. Rejections arrive as a field-keyed\n422 ({\"errors\": {\"content\": [\"can't be blank\"]}}), not the flat {error}\nbody.", + "operationId": "UpdateMyNote", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMyNoteRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateMyNote 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMyNoteResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyNotes" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/preferences.json": { + "get": { + "description": "Get the current user's preferences", + "operationId": "GetMyPreferences", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMyPreferences 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyPreferencesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update the current user's preferences.\nRejections arrive as a field-keyed 422\n({\"errors\": {\"time_zone_name\": [\"is not included in the list\"]}}), not the\nflat {error} body.", + "operationId": "UpdateMyPreferences", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMyPreferencesRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateMyPreferences 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMyPreferencesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/priorities.json": { + "post": { + "description": "Add a recording to Up Next \u2014 the current user's ordered list of prioritized\nassignments (the priorities returned by GetMyAssignments). Identify the item\nby the recording id that carries the priority; for a card table step\nsurfaced under its parent card, that is the entry's priority_recording_id.\nIdempotent: re-prioritizing an already-prioritized recording is a no-op.", + "operationId": "PrioritizeAssignment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PrioritizeAssignmentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "PrioritizeAssignment 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyAssignments" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/priorities/{recordingId}": { + "delete": { + "description": "Remove a recording from Up Next (returns 204 No Content). Exact-target:\nonly the priority carried by the identified recording is cleared, and\ndeleting an absent priority is a no-op 204 \u2014 so the DELETE is idempotent\nand safe to retry (BC3 #12483). Address a surfaced card table step by its\npriority_recording_id, not its parent card's id.", + "operationId": "DeprioritizeAssignment", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeprioritizeAssignment 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyAssignments" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/priority_moves.json": { + "post": { + "description": "Move an already-prioritized recording to a new 1-based position in Up Next\n(returns 204 No Content). NOT idempotent: a positional move's meaning\nshifts as the list changes, so a retry can land the item somewhere else \u2014\nno retry gating is declared. Errors: 400 for a missing or non-integer\nposition, 422 (flat {error} body) for an out-of-range position or an\nunprioritized recording, and a bare bodyless 404 for an inaccessible\nrecording.", + "operationId": "ReorderUpNext", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReorderUpNextRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "ReorderUpNext 204 response" + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "BareNotFoundError 404 response" + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyAssignments" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/profile.json": { + "get": { + "description": "Get the current authenticated user's profile", + "operationId": "GetMyProfile", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetMyProfile 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyProfileResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update the current authenticated user's profile (returns 204 No Content)", + "operationId": "UpdateMyProfile", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMyProfileRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UpdateMyProfile 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/question_reminders.json": { + "get": { + "description": "Get pending check-in reminders for the current user\n\nReturns questions that are pending a response from the authenticated user.\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages.", + "operationId": "GetQuestionReminders", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetQuestionReminders 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetQuestionRemindersResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-pagination": { + "style": "link", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Automation" + ] + } + }, + "/{accountId}/my/readings.json": { + "get": { + "description": "Get the current user's notification inbox (the \"Hey!\" menu).\nNotifications are grouped into unreads, reads, bubble-ups, and\nscheduled bubble-ups (`memories` remains as an always-empty\nplaceholder on BC5). Reads are paginated (50 per page). Unreads are\ncapped at 100. Bubble-ups are capped per `limit_bubble_ups`.", + "operationId": "GetMyNotifications", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through read items. Defaults to 1. This\noperation is not auto-paginated in any SDK, so a page is returned as\nasked for and later pages are not followed.", + "schema": { + "type": "integer", + "description": "Page number for paginating through read items. Defaults to 1. This\noperation is not auto-paginated in any SDK, so a page is returned as\nasked for and later pages are not followed.", + "format": "int32" + } + }, + { + "name": "limit_bubble_ups", + "in": "query", + "description": "Set to true to cap `bubble_ups` at 2 current bubble-ups and omit the\n`scheduled_bubble_ups` key entirely. Defaults to false. Use the dedicated\nbubble-ups endpoint (GetBubbleUps) to page through all current and\nscheduled bubble-ups.", + "schema": { + "type": "boolean", + "description": "Set to true to cap `bubble_ups` at 2 current bubble-ups and omit the\n`scheduled_bubble_ups` key entirely. Defaults to false. Use the dedicated\nbubble-ups endpoint (GetBubbleUps) to page through all current and\nscheduled bubble-ups." + } + } + ], + "responses": { + "200": { + "description": "GetMyNotifications 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetMyNotificationsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyNotifications" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/readings/bubble_ups.json": { + "get": { + "description": "Get the current user's current and scheduled bubble-ups (paginated, 50 per page).\nCurrent bubble-ups are returned first, ordered by most recently bubbled up;\nscheduled bubble-ups follow, ordered by scheduled bubble-up time. Each item\nuses the same notification object shape as GetMyNotifications.", + "operationId": "GetBubbleUps", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetBubbleUps 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetBubbleUpsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyNotifications" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/my/unreads.json": { + "put": { + "description": "Mark specified items as read", + "operationId": "MarkAsRead", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarkAsReadRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "MarkAsRead 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "MyNotifications" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/people.json": { + "get": { + "description": "List all people visible to the current user\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListPeople", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListPeople 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListPeopleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/people/{personId}": { + "get": { + "description": "Get a person by ID", + "operationId": "GetPerson", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetPerson 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetPersonResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/people/{personId}/out_of_office.json": { + "delete": { + "description": "Disable out of office for a person.\nAdmins on Pro Pack accounts can manage others; otherwise self only.", + "operationId": "DisableOutOfOffice", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DisableOutOfOffice 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get the out of office status for a person", + "operationId": "GetOutOfOffice", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetOutOfOffice 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetOutOfOfficeResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Enable or replace out of office for a person.\nAdmins on Pro Pack accounts can manage others; otherwise self only.", + "operationId": "EnableOutOfOffice", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnableOutOfOfficeRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "EnableOutOfOffice 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnableOutOfOfficeResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects.json": { + "get": { + "description": "List projects (active by default; optionally archived/trashed)\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListProjects", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "status", + "in": "query", + "description": "active|archived|trashed", + "schema": { + "type": "string", + "description": "active|archived|trashed" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListProjects 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListProjectsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new project", + "operationId": "CreateProject", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateProject 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "507": { + "description": "ProjectLimitError 507 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectLimitErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/recordings.json": { + "get": { + "description": "List recordings of a given type across projects\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListRecordings", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true, + "examples": { + "ListRecordings_example1": { + "summary": "List Todo recordings", + "description": "Use simple type name for basic resources", + "value": "999" + }, + "ListRecordings_example2": { + "summary": "List Kanban Card recordings", + "description": "Use double-colon notation for nested types", + "value": "999" + }, + "ListRecordings_example3": { + "summary": "List Question Answer recordings", + "description": "Another nested type example", + "value": "999" + } + } + }, + { + "name": "type", + "in": "query", + "description": "Comment|Document|Door|Kanban::Card|Kanban::Step|Message|Question::Answer|Schedule::Entry|Todo|Todolist|Upload|Vault", + "schema": { + "type": "string", + "description": "Comment|Document|Door|Kanban::Card|Kanban::Step|Message|Question::Answer|Schedule::Entry|Todo|Todolist|Upload|Vault" + }, + "required": true, + "examples": { + "ListRecordings_example1": { + "summary": "List Todo recordings", + "description": "Use simple type name for basic resources", + "value": "Todo" + }, + "ListRecordings_example2": { + "summary": "List Kanban Card recordings", + "description": "Use double-colon notation for nested types", + "value": "Kanban::Card" + }, + "ListRecordings_example3": { + "summary": "List Question Answer recordings", + "description": "Another nested type example", + "value": "Question::Answer" + } + } + }, + { + "name": "bucket", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "active|archived|trashed", + "schema": { + "type": "string", + "description": "active|archived|trashed" + } + }, + { + "name": "sort", + "in": "query", + "description": "created_at|updated_at", + "schema": { + "type": "string", + "description": "created_at|updated_at" + } + }, + { + "name": "direction", + "in": "query", + "description": "asc|desc", + "schema": { + "type": "string", + "description": "asc|desc" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListRecordings 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListRecordingsResponseContent" + }, + "examples": { + "ListRecordings_example1": { + "summary": "List Todo recordings", + "description": "Use simple type name for basic resources", + "value": [ + { + "id": 1069479523, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-02T00:00:00Z", + "title": "Ship the hardware", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/999/buckets/12345678/todos/1069479523.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/todos/1069479523", + "creator": { + "id": 1, + "name": "Someone", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z" + }, + "bucket": { + "id": 12345678, + "name": "My Project", + "type": "Project" + }, + "parent": { + "id": 987654, + "title": "Launch Tasks", + "type": "Todolist", + "url": "https://3.basecampapi.com/999/buckets/12345678/todolists/987654.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/todolists/987654" + } + } + ] + }, + "ListRecordings_example2": { + "summary": "List Kanban Card recordings", + "description": "Use double-colon notation for nested types", + "value": [ + { + "id": 1069479524, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-02T00:00:00Z", + "title": "Design the enclosure", + "inherits_status": true, + "type": "Kanban::Card", + "url": "https://3.basecampapi.com/999/buckets/12345678/card_tables/cards/1069479524.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/card_tables/cards/1069479524", + "creator": { + "id": 1, + "name": "Someone", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z" + }, + "bucket": { + "id": 12345678, + "name": "My Project", + "type": "Project" + }, + "parent": { + "id": 1069479519, + "title": "In Progress", + "type": "Kanban::Column", + "url": "https://3.basecampapi.com/999/buckets/12345678/card_tables/lists/1069479519.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/card_tables/columns/1069479519" + } + } + ] + }, + "ListRecordings_example3": { + "summary": "List Question Answer recordings", + "description": "Another nested type example", + "value": [ + { + "id": 1069479525, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-02T00:00:00Z", + "title": "How did the week go?", + "inherits_status": true, + "type": "Question::Answer", + "url": "https://3.basecampapi.com/999/buckets/12345678/question_answers/1069479525.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/question_answers/1069479525", + "creator": { + "id": 1, + "name": "Someone", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z" + }, + "bucket": { + "id": 12345678, + "name": "My Project", + "type": "Project" + }, + "parent": { + "id": 1069479518, + "title": "How did the week go?", + "type": "Question", + "url": "https://3.basecampapi.com/999/buckets/12345678/questions/1069479518.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/questions/1069479518" + } + } + ] + } + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}": { + "delete": { + "description": "Trash a project (returns 204 No Content)", + "operationId": "TrashProject", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "TrashProject 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a single project by id", + "operationId": "GetProject", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetProject 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProjectResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing project", + "operationId": "UpdateProject", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateProject 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/gauge.json": { + "put": { + "description": "Enable or disable the gauge for a project. Only project admins can toggle gauges.", + "operationId": "ToggleGauge", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ToggleGaugeRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ToggleGauge 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/gauge/needles.json": { + "get": { + "description": "List gauge needles for a project, ordered newest first.", + "operationId": "ListGaugeNeedles", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListGaugeNeedles 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListGaugeNeedlesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a gauge needle (progress update) for a project", + "operationId": "CreateGaugeNeedle", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGaugeNeedleRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateGaugeNeedle 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateGaugeNeedleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/people.json": { + "get": { + "description": "List all active people on a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListProjectPeople", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListProjectPeople 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListProjectPeopleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/people/users.json": { + "put": { + "description": "Update project access (grant/revoke/create people)", + "operationId": "UpdateProjectAccess", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectAccessRequestContent" + }, + "examples": { + "UpdateProjectAccess_example1": { + "summary": "Grant access to existing users", + "description": "Use grant array with person IDs", + "value": { + "grant": [ + 111, + 222 + ] + } + }, + "UpdateProjectAccess_example2": { + "summary": "Revoke access", + "description": "Use revoke array to remove users", + "value": { + "revoke": [ + 333 + ] + } + }, + "UpdateProjectAccess_example3": { + "summary": "Invite new users", + "description": "Use create array for new users without accounts", + "value": { + "create": [ + { + "name": "Jane", + "email_address": "jane@example.com" + } + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true, + "examples": { + "UpdateProjectAccess_example1": { + "summary": "Grant access to existing users", + "description": "Use grant array with person IDs", + "value": "999" + }, + "UpdateProjectAccess_example2": { + "summary": "Revoke access", + "description": "Use revoke array to remove users", + "value": "999" + }, + "UpdateProjectAccess_example3": { + "summary": "Invite new users", + "description": "Use create array for new users without accounts", + "value": "999" + } + } + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true, + "examples": { + "UpdateProjectAccess_example1": { + "summary": "Grant access to existing users", + "description": "Use grant array with person IDs", + "value": 12345678 + }, + "UpdateProjectAccess_example2": { + "summary": "Revoke access", + "description": "Use revoke array to remove users", + "value": 12345678 + }, + "UpdateProjectAccess_example3": { + "summary": "Invite new users", + "description": "Use create array for new users without accounts", + "value": 12345678 + } + } + } + ], + "responses": { + "200": { + "description": "UpdateProjectAccess 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProjectAccessResponseContent" + }, + "examples": { + "UpdateProjectAccess_example1": { + "summary": "Grant access to existing users", + "description": "Use grant array with person IDs", + "value": { + "granted": [ + { + "id": 111, + "name": "Jane Doe" + }, + { + "id": 222, + "name": "John Roe" + } + ], + "revoked": [] + } + }, + "UpdateProjectAccess_example2": { + "summary": "Revoke access", + "description": "Use revoke array to remove users", + "value": { + "granted": [], + "revoked": [ + { + "id": 333, + "name": "Sam Ray" + } + ] + } + }, + "UpdateProjectAccess_example3": { + "summary": "Invite new users", + "description": "Use create array for new users without accounts", + "value": { + "granted": [ + { + "id": 444, + "name": "Jane", + "email_address": "jane@example.com" + } + ], + "revoked": [] + } + } + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/status/active.json": { + "put": { + "description": "Restore a project to active status from trash as well as from the archive (returns 204 No Content).\nThis is the inverse of both ArchiveProject and TrashProject. Restoring counts against\nthe account's project limit, so it answers 507 when that limit is already reached.", + "operationId": "UnarchiveProject", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UnarchiveProject 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "507": { + "description": "ProjectLimitError 507 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectLimitErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/status/archived.json": { + "put": { + "description": "Archive a project, removing it from the active project list (returns 204 No Content).\nAccounts on the admin pro pack may restrict archiving to admins and the project's\ncreator, which answers 403.", + "operationId": "ArchiveProject", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "ArchiveProject 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Projects" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/projects/{projectId}/timeline.json": { + "get": { + "description": "Get project timeline", + "operationId": "GetProjectTimeline", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetProjectTimeline 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProjectTimelineResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/projects/{projectId}/timesheet.json": { + "get": { + "description": "Get timesheet for a specific project", + "operationId": "GetProjectTimesheet", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "projectId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "from", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "person_id", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetProjectTimesheet 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProjectTimesheetResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/question_answers/{answerId}": { + "get": { + "description": "Get a single answer by id", + "operationId": "GetAnswer", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "answerId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetAnswer 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAnswerResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing answer", + "operationId": "UpdateAnswer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionAnswerUpdatePayload" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "answerId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UpdateAnswer 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/questionnaires/{questionnaireId}": { + "get": { + "description": "Get a questionnaire (automatic check-ins container) by id", + "operationId": "GetQuestionnaire", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionnaireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetQuestionnaire 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetQuestionnaireResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/questionnaires/{questionnaireId}/questions.json": { + "get": { + "description": "List all questions in a questionnaire\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListQuestions", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionnaireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListQuestions 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListQuestionsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new question in a questionnaire", + "operationId": "CreateQuestion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateQuestionRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionnaireId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateQuestion 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateQuestionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/questions/{questionId}": { + "get": { + "description": "Get a single question by id", + "operationId": "GetQuestion", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetQuestion 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetQuestionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing question", + "operationId": "UpdateQuestion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateQuestionRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateQuestion 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateQuestionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/questions/{questionId}/answers.json": { + "get": { + "description": "List all answers for a question\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListAnswers", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListAnswers 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListAnswersResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new answer for a question", + "operationId": "CreateAnswer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionAnswerPayload" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateAnswer 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAnswerResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/questions/{questionId}/answers/by.json": { + "get": { + "description": "List all people who have answered a question (answerers)\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages.", + "operationId": "ListQuestionAnswerers", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListQuestionAnswerers 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListQuestionAnswerersResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-pagination": { + "style": "link", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Automation" + ] + } + }, + "/{accountId}/questions/{questionId}/answers/by/{personId}": { + "get": { + "description": "Get all answers from a specific person for a question\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages.", + "operationId": "GetAnswersByPerson", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetAnswersByPerson 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAnswersByPersonResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-pagination": { + "style": "link", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Automation" + ] + } + }, + "/{accountId}/questions/{questionId}/notification_settings.json": { + "put": { + "description": "Update notification settings for a check-in question", + "operationId": "UpdateQuestionNotificationSettings", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateQuestionNotificationSettingsRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateQuestionNotificationSettings 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateQuestionNotificationSettingsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Automation" + ] + } + }, + "/{accountId}/questions/{questionId}/pause.json": { + "delete": { + "description": "Resume a paused check-in question (resumes sending reminders)", + "operationId": "ResumeQuestion", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ResumeQuestion 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResumeQuestionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Automation" + ] + }, + "post": { + "description": "Pause a check-in question (stops sending reminders)", + "operationId": "PauseQuestion", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "questionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "PauseQuestion 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PauseQuestionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Automation" + ] + } + }, + "/{accountId}/recordings/{messageId}/pin.json": { + "delete": { + "description": "Unpin a message from the message board", + "operationId": "UnpinMessage", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "messageId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UnpinMessage 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Pin a message to the top of the message board", + "operationId": "PinMessage", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "messageId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "PinMessage 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/bookmark.json": { + "delete": { + "description": "Remove the current user's bookmark from a recording (returns 204 No Content).\nIdempotent: deleting an absent bookmark also returns 204.", + "operationId": "DeleteBookmark", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteBookmark 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Bookmarks" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Report whether the current user has bookmarked the recording.", + "operationId": "GetBookmark", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetBookmark 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetBookmarkResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Bookmarks" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Bookmark a recording for the current user.\nIdempotent: re-bookmarking returns the existing bookmark, never a duplicate.", + "operationId": "CreateBookmark", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateBookmark 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBookmarkResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Bookmarks" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/boosts.json": { + "get": { + "description": "List boosts on a recording", + "operationId": "ListRecordingBoosts", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListRecordingBoosts 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListRecordingBoostsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boosts" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a boost on a recording", + "operationId": "CreateRecordingBoost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRecordingBoostRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateRecordingBoost 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRecordingBoostResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boosts" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/client_visibility.json": { + "put": { + "description": "Set client visibility for a recording", + "operationId": "SetClientVisibility", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetClientVisibilityRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "SetClientVisibility 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetClientVisibilityResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "ClientFeatures" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/comments.json": { + "get": { + "description": "List comments on a recording\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListComments", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListComments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListCommentsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new comment on a recording", + "operationId": "CreateComment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCommentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateComment 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCommentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/events.json": { + "get": { + "description": "List all events for a recording\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListEvents", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListEvents 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListEventsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/events/{eventId}/boosts.json": { + "get": { + "description": "List boosts on a specific event within a recording", + "operationId": "ListEventBoosts", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListEventBoosts 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListEventBoostsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boosts" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a boost on a specific event within a recording", + "operationId": "CreateEventBoost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEventBoostRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "eventId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateEventBoost 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEventBoostResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boosts" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/status/active.json": { + "put": { + "description": "Unarchive a recording (restore to active status)", + "operationId": "UnarchiveRecording", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UnarchiveRecording 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/status/archived.json": { + "put": { + "description": "Archive a recording", + "operationId": "ArchiveRecording", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "ArchiveRecording 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/status/trashed.json": { + "put": { + "description": "Trash a recording", + "operationId": "TrashRecording", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true, + "examples": { + "TrashRecording_example1": { + "summary": "Trash any recording type", + "description": "Works on comments, messages, documents, cards - any recording", + "value": "999" + } + } + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true, + "examples": { + "TrashRecording_example1": { + "summary": "Trash any recording type", + "description": "Works on comments, messages, documents, cards - any recording", + "value": 555666 + } + } + } + ], + "responses": { + "204": { + "description": "TrashRecording 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/subscription.json": { + "delete": { + "description": "Unsubscribe the current user from a recording", + "operationId": "Unsubscribe", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "Unsubscribe 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get subscription information for a recording", + "operationId": "GetSubscription", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSubscription 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSubscriptionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Subscribe the current user to a recording", + "operationId": "Subscribe", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Subscribe 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscribeResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update subscriptions by adding or removing specific users", + "operationId": "UpdateSubscription", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSubscriptionRequestContent" + }, + "examples": { + "UpdateSubscription_example1": { + "summary": "Add subscribers", + "description": "", + "value": { + "subscriptions": [ + 111, + 222 + ] + } + }, + "UpdateSubscription_example2": { + "summary": "Remove subscribers", + "description": "", + "value": { + "unsubscriptions": [ + 333 + ] + } + } + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true, + "examples": { + "UpdateSubscription_example1": { + "summary": "Add subscribers", + "description": "", + "value": "999" + }, + "UpdateSubscription_example2": { + "summary": "Remove subscribers", + "description": "", + "value": "999" + } + } + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true, + "examples": { + "UpdateSubscription_example1": { + "summary": "Add subscribers", + "description": "", + "value": 987654 + }, + "UpdateSubscription_example2": { + "summary": "Remove subscribers", + "description": "", + "value": 987654 + } + } + } + ], + "responses": { + "200": { + "description": "UpdateSubscription 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSubscriptionResponseContent" + }, + "examples": { + "UpdateSubscription_example1": { + "summary": "Add subscribers", + "description": "", + "value": { + "subscribed": true, + "count": 2, + "url": "https://3.basecampapi.com/999/buckets/12345678/recordings/987654/subscription.json", + "subscribers": [ + { + "id": 111, + "name": "Jane Doe" + }, + { + "id": 222, + "name": "John Roe" + } + ] + } + }, + "UpdateSubscription_example2": { + "summary": "Remove subscribers", + "description": "", + "value": { + "subscribed": false, + "count": 0, + "url": "https://3.basecampapi.com/999/buckets/12345678/recordings/987654/subscription.json", + "subscribers": [] + } + } + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "People" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/timesheet.json": { + "get": { + "description": "Get timesheet for a specific recording", + "operationId": "GetRecordingTimesheet", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "from", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "person_id", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetRecordingTimesheet 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetRecordingTimesheetResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{recordingId}/timesheet/entries.json": { + "post": { + "description": "Create a timesheet entry on a recording", + "operationId": "CreateTimesheetEntry", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTimesheetEntryRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "recordingId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTimesheetEntry 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTimesheetEntryResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/recordings/{toolId}/position.json": { + "delete": { + "description": "Disable a tool (hide it from the project dock)", + "operationId": "DisableTool", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "toolId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DisableTool 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Enable a tool (show it on the project dock)", + "operationId": "EnableTool", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "toolId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "EnableTool 201 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Reposition a tool on the project dock", + "operationId": "RepositionTool", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositionToolRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "toolId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "RepositionTool 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/reports/gauges.json": { + "get": { + "description": "List gauges across all projects the authenticated user has access to.\nGauges are sorted by risk level (red, yellow, green), then alphabetically.", + "operationId": "ListGauges", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "bucket_ids", + "in": "query", + "description": "Comma-separated list of project IDs. When provided, results are returned\nin the order specified instead of by risk level.", + "schema": { + "type": "string", + "description": "Comma-separated list of project IDs. When provided, results are returned\nin the order specified instead of by risk level." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListGauges 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListGaugesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Gauges" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/reports/progress.json": { + "get": { + "description": "Get account-wide activity feed (progress report)", + "operationId": "GetProgressReport", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetProgressReport 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProgressReportResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/reports/schedules/upcoming.json": { + "get": { + "description": "Get upcoming schedule entries and assignable items within a date window.\nThis endpoint is preserved as the canonical API path on BC5;\nthe BC5 `/calendar` web view is HTML-only.\n\nThe three arrays carry the report's own reduced projections \u2014\nUpcomingScheduleEntry and UpcomingAssignable \u2014 not the shared ScheduleEntry\nand Todo/Card shapes. BC3 renders this report through\n`app/views/api/schedules/calendar/_entry.json.jbuilder` and\n`_assignable.json.jbuilder`, which emit a narrower key set than the\nper-resource partials, plus keys those partials never emit. See\nUpcomingScheduleEntry for the full accounting.", + "operationId": "GetUpcomingSchedule", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "window_starts_on", + "in": "query", + "description": "Inclusive first day of the window, `YYYY-MM-DD`. Required \u2014 BC3 answers 400 without it.", + "schema": { + "type": "string", + "description": "Inclusive first day of the window, `YYYY-MM-DD`. Required \u2014 BC3 answers 400 without it." + }, + "required": true + }, + { + "name": "window_ends_on", + "in": "query", + "description": "Inclusive last day of the window, `YYYY-MM-DD`. Required \u2014 BC3 answers 400 without it.", + "schema": { + "type": "string", + "description": "Inclusive last day of the window, `YYYY-MM-DD`. Required \u2014 BC3 answers 400 without it." + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetUpcomingSchedule 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetUpcomingScheduleResponseContent" + } + } + } + }, + "400": { + "description": "BadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/reports/timesheet.json": { + "get": { + "description": "Get account-wide timesheet report", + "operationId": "GetTimesheetReport", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "from", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "to", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "person_id", + "in": "query", + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "GetTimesheetReport 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTimesheetReportResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/reports/todos/assigned.json": { + "get": { + "description": "List people who can be assigned todos", + "operationId": "ListAssignablePeople", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListAssignablePeople 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListAssignablePeopleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/reports/todos/assigned/{personId}": { + "get": { + "description": "Get todos assigned to a specific person", + "operationId": "GetAssignedTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "group_by", + "in": "query", + "description": "Group by \"bucket\" or \"date\"", + "schema": { + "type": "string", + "description": "Group by \"bucket\" or \"date\"" + } + } + ], + "responses": { + "200": { + "description": "GetAssignedTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAssignedTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/reports/todos/overdue.json": { + "get": { + "description": "Get overdue todos grouped by lateness", + "operationId": "GetOverdueTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetOverdueTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetOverdueTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/reports/users/progress/{personId}.json": { + "get": { + "description": "Get a person's activity timeline", + "operationId": "GetPersonProgress", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "personId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetPersonProgress 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetPersonProgressResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50, + "key": "events" + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Reports" + ] + } + }, + "/{accountId}/schedule_entries/{entryId}": { + "get": { + "description": "Get a single schedule entry by id.\nNote: Recurring entries will redirect (302) to their recordable URL.\nUse GetScheduleEntryOccurrence for recurring entries instead.", + "operationId": "GetScheduleEntry", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "entryId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetScheduleEntry 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetScheduleEntryResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace a schedule entry with a new complete representation.\nThe request body is the entry's full writable state: a writable field\nomitted from the request is cleared server-side, because BC3 builds a\nbrand-new Schedule::Entry from the permitted params and swaps the recordable\nwholesale.\nThree writable fields are carved out of that swap and preserved when the\nrequest does not address them \u2014 participant_ids, url and highlighted, as\ndeclared by preservedOnOmission. Each is a field a caller could not safely\nresend from a read-back, which is why the guard is server-side: the response\ncarries participants (objects, not ids) and join_url (the entry's own url\nkey is its Basecamp API URL, a different value under a colliding name).\nAddressing one applies it normally, so participant_ids: [] clears the\nparticipants, url: \"\" clears the join link and highlighted: false removes\nthe highlight.\nstarts_at and ends_at are required: Schedule::Entry validates their presence\nand Recording validates the associated recordable on update, so omitting\neither is a 422 rather than a clear. summary carries no validation \u2014 omit it\nand the entry reads back as \"Untitled\" (Schedule::Entry#summary falls back\nwhen blank).\nRecurring entries are unreachable here. ensure_non_recurring_event redirects\nboth show and update to the entry's occurrence, so this operation serves\nnon-recurring entries only; read a recurring entry through\nGetScheduleEntryOccurrence.\ntime_zone_name, recurs_until and recurrence_schedule are not modeled: BC3\nforces all three to nil for a non-recurring entry, which is the only kind\nthis route serves.\nSubscribers follow the same carve-out logic one level up. A drafted entry\nkeeps its current subscribers when the request addresses neither\nsubscriptions, notify, nor the participant parameters.\nTo set some fields while preserving the rest, use the SDK's merge-safe\nupdate or edit methods, which GET the current entry and PUT the full\nrepresentation back. Those read-modify-write helpers are not atomic:\na concurrent write between the GET and PUT is overwritten (last write\nwins for the whole representation; the window is one round-trip).", + "operationId": "ReplaceScheduleEntry", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceScheduleEntryRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "entryId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ReplaceScheduleEntry 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceScheduleEntryResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "x-basecamp-write-semantics": { + "mode": "replace", + "clearsOmitted": true, + "preservedOnOmission": [ + "participant_ids", + "url", + "highlighted" + ] + } + } + }, + "/{accountId}/schedule_entries/{entryId}/occurrences/{date}": { + "get": { + "description": "Get a specific occurrence of a recurring schedule entry", + "operationId": "GetScheduleEntryOccurrence", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "entryId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "date", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetScheduleEntryOccurrence 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetScheduleEntryOccurrenceResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/schedules/{scheduleId}": { + "get": { + "description": "Get a schedule", + "operationId": "GetSchedule", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "scheduleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSchedule 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetScheduleResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update schedule settings", + "operationId": "UpdateScheduleSettings", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateScheduleSettingsRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "scheduleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateScheduleSettings 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateScheduleSettingsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/schedules/{scheduleId}/entries.json": { + "get": { + "description": "List entries on a schedule\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListScheduleEntries", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "scheduleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "status", + "in": "query", + "description": "active|archived|trashed", + "schema": { + "type": "string", + "description": "active|archived|trashed" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListScheduleEntries 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListScheduleEntriesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new schedule entry", + "operationId": "CreateScheduleEntry", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateScheduleEntryRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "scheduleId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateScheduleEntry 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateScheduleEntryResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/search.json": { + "get": { + "description": "Search for content across the account", + "operationId": "Search", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "type_names[]", + "in": "query", + "description": "Recording types to include. Use `key` values from the metadata\nendpoint's `recording_search_types`. Available since Basecamp 5.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Recording types to include. Use `key` values from the metadata\nendpoint's `recording_search_types`. Available since Basecamp 5." + }, + "explode": true + }, + { + "name": "bucket_ids[]", + "in": "query", + "description": "Project IDs to filter by. Available since Basecamp 5.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Project IDs to filter by. Available since Basecamp 5." + }, + "explode": true + }, + { + "name": "creator_ids[]", + "in": "query", + "description": "Creator person IDs to filter by. Available since Basecamp 5.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Creator person IDs to filter by. Available since Basecamp 5." + }, + "explode": true + }, + { + "name": "file_type", + "in": "query", + "description": "Filter attachments by type. Use `key` values from the metadata\nendpoint's `file_search_types`.", + "schema": { + "type": "string", + "description": "Filter attachments by type. Use `key` values from the metadata\nendpoint's `file_search_types`." + } + }, + { + "name": "exclude_chat", + "in": "query", + "description": "Set to true to exclude chat results.", + "schema": { + "type": "boolean", + "description": "Set to true to exclude chat results." + } + }, + { + "name": "since", + "in": "query", + "description": "last_7_days|last_30_days|last_90_days|last_12_months|forever", + "schema": { + "type": "string", + "description": "last_7_days|last_30_days|last_90_days|last_12_months|forever" + } + }, + { + "name": "sort", + "in": "query", + "description": "best_match|recency", + "schema": { + "type": "string", + "description": "best_match|recency" + } + }, + { + "name": "type", + "in": "query", + "description": "Deprecated: prefer type_names[].", + "schema": { + "type": "string", + "deprecated": true, + "description": "Deprecated: prefer type_names[].\nThis shape is deprecated since 2026-07: Use typeNames (type_names[]) instead" + }, + "deprecated": true, + "x-deprecated-reason": "prefer type_names[]." + }, + { + "name": "bucket_id", + "in": "query", + "description": "Deprecated: prefer bucket_ids[].", + "schema": { + "type": "integer", + "deprecated": true, + "description": "Deprecated: prefer bucket_ids[].\nThis shape is deprecated since 2026-07: Use bucketIds (bucket_ids[]) instead", + "format": "int64" + }, + "deprecated": true, + "x-deprecated-reason": "prefer bucket_ids[]." + }, + { + "name": "creator_id", + "in": "query", + "description": "Deprecated: prefer creator_ids[].", + "schema": { + "type": "integer", + "deprecated": true, + "description": "Deprecated: prefer creator_ids[].\nThis shape is deprecated since 2026-07: Use creatorIds (creator_ids[]) instead", + "format": "int64" + }, + "deprecated": true, + "x-deprecated-reason": "prefer creator_ids[]." + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Search 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/searches/metadata.json": { + "get": { + "description": "Get search metadata (available filter options)", + "operationId": "GetSearchMetadata", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetSearchMetadata 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetSearchMetadataResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/stacks.json": { + "get": { + "description": "List the authenticated user's folders in home-screen order.\n\nReturns a bare array with no pagination envelope. Items are the base folder\nshape: they carry `bucket_ids` but **not** the expanded `projects`, which\nonly the single-folder operations return.", + "operationId": "ListFolders", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListFolders 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFoldersResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Folders" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a folder for the authenticated user and file the given projects into it.\n\nReturns 201 with the new folder and its expanded `projects`, placed at the\ntop of the home screen. Filing an all-access project the user has not joined\n**grants** them access to it. Every id is preflighted: if any is archived,\ntrashed, or an invitation-only project the user is not on, the whole request\nfails with 404 and nothing is created \u2014 there is no partial success.", + "operationId": "CreateFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFolderRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateFolder 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFolderResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Folders" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/stacks/{folderId}": { + "delete": { + "description": "Delete a folder and unpin its projects from the home screen (returns 204 No Content).\n\nThe projects themselves are not deleted and are not moved back out onto the\nhome screen; they simply stop appearing there until pinned again.", + "operationId": "DeleteFolder", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "folderId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteFolder 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Folders" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get one folder, with the projects grouped inside it expanded under `projects`.", + "operationId": "GetFolder", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "folderId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetFolder 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetFolderResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Folders" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Rename a folder.\n\n`name` is the only writable attribute; a folder's projects, ordering, and\nimage are managed elsewhere and an image parameter sent here is ignored.", + "operationId": "UpdateFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateFolderRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "folderId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateFolder 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateFolderResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "FieldValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Folders" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/templates.json": { + "get": { + "description": "List all templates visible to the current user\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListTemplates", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "status", + "in": "query", + "description": "active|archived|trashed", + "schema": { + "type": "string", + "description": "active|archived|trashed" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListTemplates 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTemplatesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new template", + "operationId": "CreateTemplate", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTemplateRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTemplate 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTemplateResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/templates/{templateId}": { + "delete": { + "description": "Delete a template (trash it)", + "operationId": "DeleteTemplate", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "templateId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteTemplate 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a single template by id", + "operationId": "GetTemplate", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "templateId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetTemplate 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTemplateResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing template", + "operationId": "UpdateTemplate", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTemplateRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "templateId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateTemplate 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTemplateResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/templates/{templateId}/project_constructions.json": { + "post": { + "description": "Create a project from a template (asynchronous)", + "operationId": "CreateProjectFromTemplate", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectFromTemplateRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "templateId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateProjectFromTemplate 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectFromTemplateResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/templates/{templateId}/project_constructions/{constructionId}": { + "get": { + "description": "Get the status of a project construction", + "operationId": "GetProjectConstruction", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "templateId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "constructionId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetProjectConstruction 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetProjectConstructionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/timesheet_entries/{entryId}": { + "delete": { + "description": "Permanently delete a timesheet entry; answers 403 when the caller may not archive or trash it.", + "operationId": "DestroyTimesheetEntry", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "entryId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DestroyTimesheetEntry 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a single timesheet entry", + "operationId": "GetTimesheetEntry", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "entryId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetTimesheetEntry 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTimesheetEntryResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update a timesheet entry", + "operationId": "UpdateTimesheetEntry", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTimesheetEntryRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "entryId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateTimesheetEntry 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTimesheetEntryResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Schedule" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todolists/groups/{groupId}/position.json": { + "put": { + "description": "Reposition a todolist group", + "operationId": "RepositionTodolistGroup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositionTodolistGroupRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "groupId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "RepositionTodolistGroup 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todolists/{id}": { + "get": { + "description": "Get a single todolist or todolist group by id\nThe endpoint is polymorphic, but it answers with one shape: BC3 has no group\nmodel, so both variants come back as a flat Todolist (see the Todolist shape).", + "operationId": "GetTodolistOrGroup", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true, + "examples": { + "GetTodolistOrGroup_example1": { + "summary": "Get a to-do list", + "description": "A to-do list: parent is a Todoset, and groups_url is present", + "value": "999" + }, + "GetTodolistOrGroup_example2": { + "summary": "Get a group", + "description": "A group: parent is a Todolist, and group_position_url replaces groups_url", + "value": "999" + } + } + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true, + "examples": { + "GetTodolistOrGroup_example1": { + "summary": "Get a to-do list", + "description": "A to-do list: parent is a Todoset, and groups_url is present", + "value": 987654 + }, + "GetTodolistOrGroup_example2": { + "summary": "Get a group", + "description": "A group: parent is a Todolist, and group_position_url replaces groups_url", + "value": 111222 + } + } + } + ], + "responses": { + "200": { + "description": "GetTodolistOrGroup 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTodolistOrGroupResponseContent" + }, + "examples": { + "GetTodolistOrGroup_example1": { + "summary": "Get a to-do list", + "description": "A to-do list: parent is a Todoset, and groups_url is present", + "value": { + "id": 987654, + "status": "active", + "name": "Launch Tasks", + "visible_to_clients": false, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z", + "title": "Launch Tasks", + "inherits_status": true, + "type": "Todolist", + "description": "", + "description_attachments": [], + "url": "https://3.basecampapi.com/999/buckets/12345678/todolists/987654.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/todolists/987654", + "bubble_up_url": "https://3.basecampapi.com/999/buckets/12345678/recordings/987654/bubble_up.json", + "creator": { + "id": 1, + "name": "Someone", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z" + }, + "bucket": { + "id": 12345678, + "name": "My Project", + "type": "Project" + }, + "parent": { + "id": 99999, + "title": "To-dos", + "type": "Todoset", + "url": "https://3.basecampapi.com/999/buckets/12345678/todosets/99999.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/todosets/99999" + }, + "comments_app_url": "https://3.basecamp.com/999/buckets/12345678/recordings/987654/comments", + "groups_url": "https://3.basecampapi.com/999/buckets/12345678/todolists/987654/groups.json", + "color": "blue" + } + }, + "GetTodolistOrGroup_example2": { + "summary": "Get a group", + "description": "A group: parent is a Todolist, and group_position_url replaces groups_url", + "value": { + "id": 111222, + "status": "active", + "name": "Q1 Milestones", + "visible_to_clients": false, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z", + "title": "Q1 Milestones", + "inherits_status": true, + "type": "Todolist", + "description": "", + "description_attachments": [], + "url": "https://3.basecampapi.com/999/buckets/12345678/todolists/111222.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/todolists/111222", + "bubble_up_url": "https://3.basecampapi.com/999/buckets/12345678/recordings/111222/bubble_up.json", + "creator": { + "id": 1, + "name": "Someone", + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z" + }, + "bucket": { + "id": 12345678, + "name": "My Project", + "type": "Project" + }, + "parent": { + "id": 987654, + "title": "Launch Tasks", + "type": "Todolist", + "url": "https://3.basecampapi.com/999/buckets/12345678/todolists/987654.json", + "app_url": "https://3.basecamp.com/999/buckets/12345678/todolists/987654" + }, + "comments_app_url": "https://3.basecamp.com/999/buckets/12345678/recordings/111222/comments", + "group_position_url": "https://3.basecampapi.com/999/buckets/12345678/todolists/groups/111222/position.json", + "color": null + } + } + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace a todolist (or todolist group) with a new complete representation.\nThe endpoint is polymorphic - it addresses a to-do list or a group, and answers\nwith the same flat Todolist shape either way.\nThe request body is the recordable's full writable state: TodolistsController#update\nbuilds a brand-new Todolist from the permitted params and swaps it in, so any\nwritable field omitted from the request is cleared server-side (a request that\nomits description erases the description). name is required - it is\npresence-validated on the model, so a request without it is rejected.\nTo set some fields while preserving the rest, use the SDK's merge-safe\nupdate or edit methods, which GET the current list and PUT the full\nrepresentation back. Those read-modify-write helpers are not atomic:\na concurrent write between the GET and PUT is overwritten (last write\nwins for the whole representation; the window is one round-trip).", + "operationId": "UpdateTodolistOrGroup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTodolistOrGroupRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateTodolistOrGroup 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTodolistOrGroupResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "x-basecamp-write-semantics": { + "mode": "replace", + "clearsOmitted": true + } + } + }, + "/{accountId}/todolists/{todolistId}/groups.json": { + "get": { + "description": "List groups in a todolist\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListTodolistGroups", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todolistId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListTodolistGroups 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTodolistGroupsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new group in a todolist", + "operationId": "CreateTodolistGroup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodolistGroupRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todolistId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTodolistGroup 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodolistGroupResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todolists/{todolistId}/todos.json": { + "get": { + "description": "List todos in a todolist\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todolistId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "status", + "in": "query", + "description": "active|archived|trashed", + "schema": { + "type": "string", + "description": "active|archived|trashed" + } + }, + { + "name": "completed", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new todo in a todolist", + "operationId": "CreateTodo", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodoRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todolistId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTodo 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodoResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/completed.json": { + "get": { + "description": "Completed to-dos across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingCompletedTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingCompletedTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCompletedTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/no_due_date.json": { + "get": { + "description": "Open to-dos with no due date across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingNoDueDateTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingNoDueDateTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingNoDueDateTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/open.json": { + "get": { + "description": "Active, incomplete to-dos across all accessible projects, grouped by project (paginated).\nEach bucket entry carries the matching to-dos and their steps.", + "operationId": "GetEverythingOpenTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingOpenTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOpenTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/overdue.json": { + "get": { + "description": "Get every overdue to-do across all accessible projects, oldest-due-date-first.\nA complete, unpaginated array; each item embeds its `bucket`.", + "operationId": "GetEverythingOverdueTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + } + ], + "responses": { + "200": { + "description": "GetEverythingOverdueTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOverdueTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/unassigned.json": { + "get": { + "description": "Open, unassigned to-dos across all accessible projects, grouped by project (paginated).", + "operationId": "GetEverythingUnassignedTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "assignee_ids[]", + "in": "query", + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered.", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to tasks assigned to at least one of the given people (repeatable).\nAssignees on nested steps are not considered." + }, + "explode": true + }, + { + "name": "due", + "in": "query", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored.", + "schema": { + "type": "string", + "description": "Filter by due date: with, without, or overdue. Unrecognized values are ignored." + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingUnassignedTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingUnassignedTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 5 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/{todoId}": { + "get": { + "description": "Get a single todo by id", + "operationId": "GetTodo", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todoId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetTodo 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTodoResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Replace a todo with a new complete representation.\nThe request body is the todo's full writable state: any writable field\nomitted from the request is cleared server-side (empty/missing\nassignee_ids clears assignees, missing description clears it, and so\non). content is required \u2014 a request without it is rejected.\nTo set some fields while preserving the rest, use the SDK's merge-safe\nupdate or edit methods, which GET the current todo and PUT the full\nrepresentation back. Those read-modify-write helpers are not atomic:\na concurrent write between the GET and PUT is overwritten (last write\nwins for the whole representation; the window is one round-trip).", + "operationId": "ReplaceTodo", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceTodoRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todoId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ReplaceTodo 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReplaceTodoResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "x-basecamp-write-semantics": { + "mode": "replace", + "clearsOmitted": true + } + } + }, + "/{accountId}/todos/{todoId}/completion.json": { + "delete": { + "description": "Mark a todo as incomplete", + "operationId": "UncompleteTodo", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todoId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "UncompleteTodo 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Mark a todo as complete", + "operationId": "CompleteTodo", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todoId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "CompleteTodo 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todos/{todoId}/position.json": { + "put": { + "description": "Reposition a todo within its todolist", + "operationId": "RepositionTodo", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositionTodoRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todoId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "RepositionTodo 200 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "tags": [ + "Todos" + ] + } + }, + "/{accountId}/todosets/todolists/{todolistId}/position.json": { + "put": { + "description": "Reposition a to-do list within its to-do set.\nposition is the 1-based index among the to-do lists the caller can see; the server\ntranslates it relative to loose to-dos and hidden completed lists. Shifts siblings.", + "operationId": "RepositionTodolist", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepositionTodolistRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todolistId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "RepositionTodolist 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todosets/{todosetId}": { + "get": { + "description": "Get a todoset (container for todolists in a project)", + "operationId": "GetTodoset", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todosetId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetTodoset 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetTodosetResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todosets/{todosetId}/hill.json": { + "get": { + "description": "Get the hill chart for a todoset", + "operationId": "GetHillChart", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todosetId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetHillChart 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetHillChartResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todosets/{todosetId}/hills/settings.json": { + "put": { + "description": "Track or untrack todolists on a hill chart", + "operationId": "UpdateHillChartSettings", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateHillChartSettingsRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todosetId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateHillChartSettings 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateHillChartSettingsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/todosets/{todosetId}/todolists.json": { + "get": { + "description": "List todolists in a todoset\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListTodolists", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todosetId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "status", + "in": "query", + "description": "active|archived|trashed", + "schema": { + "type": "string", + "description": "active|archived|trashed" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListTodolists 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTodolistsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new todolist in a todoset", + "operationId": "CreateTodolist", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodolistRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "todosetId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateTodolist 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTodolistResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Todos" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/uploads/{uploadId}": { + "get": { + "description": "Get a single upload by id", + "operationId": "GetUpload", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "uploadId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetUpload 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetUploadResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing upload", + "operationId": "UpdateUpload", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUploadRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "uploadId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateUpload 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUploadResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/uploads/{uploadId}/versions.json": { + "get": { + "description": "List versions of an upload\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListUploadVersions", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "uploadId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ListUploadVersions 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListUploadVersionsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Replace an upload's file with a new version\n\nThe recording keeps its id, its URL and its comments; the previous file becomes a\npast version. Use this instead of CreateUpload when publishing a new release of the\nsame file, so its published link keeps working.", + "operationId": "CreateUploadVersion", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUploadVersionRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "uploadId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateUploadVersion 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUploadVersionResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "507": { + "description": "StorageLimitError 507 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StorageLimitErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/vaults/{vaultId}": { + "get": { + "description": "Get a single vault by id", + "operationId": "GetVault", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetVault 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetVaultResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing vault", + "operationId": "UpdateVault", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateVaultRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateVault 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateVaultResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/vaults/{vaultId}/documents.json": { + "get": { + "description": "List documents in a vault\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListDocuments", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListDocuments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListDocumentsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new document in a vault", + "operationId": "CreateDocument", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDocumentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateDocument 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDocumentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/vaults/{vaultId}/uploads.json": { + "get": { + "description": "List uploads in a vault\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListUploads", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListUploads 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListUploadsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new upload in a vault", + "operationId": "CreateUpload", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUploadRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateUpload 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUploadResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "507": { + "description": "StorageLimitError 507 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StorageLimitErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/vaults/{vaultId}/vaults.json": { + "get": { + "description": "List vaults (subfolders) in a vault\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", + "operationId": "ListVaults", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "ListVaults 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListVaultsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "post": { + "description": "Create a new vault (subfolder) in a vault", + "operationId": "CreateVault", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVaultRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "vaultId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateVault 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateVaultResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Files" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/webhooks/{webhookId}": { + "delete": { + "description": "Delete a webhook", + "operationId": "DeleteWebhook", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "webhookId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "DeleteWebhook 204 response" + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "get": { + "description": "Get a single webhook by id", + "operationId": "GetWebhook", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "webhookId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetWebhook 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetWebhookResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing webhook", + "operationId": "UpdateWebhook", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWebhookRequestContent" + } + } + } + }, + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "webhookId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "UpdateWebhook 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWebhookResponseContent" + } + } + } + }, + "400": { + "description": "BareFieldBadRequestError 400 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BareFieldBadRequestErrorResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "507": { + "description": "WebhookLimitError 507 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookLimitErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Automation" + ], + "x-basecamp-idempotent": { + "natural": true + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + } + }, + "components": { + "schemas": { + "Account": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "owner_name": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "trial": { + "type": "boolean" + }, + "trial_ends_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "frozen": { + "type": "boolean" + }, + "paused": { + "type": "boolean" + }, + "limits": { + "$ref": "#/components/schemas/AccountLimits" + }, + "subscription": { + "$ref": "#/components/schemas/AccountSubscription" + }, + "settings": { + "$ref": "#/components/schemas/AccountSettings" + }, + "logo": { + "$ref": "#/components/schemas/AccountLogo" + } + }, + "required": [ + "created_at", + "id", + "name", + "updated_at" + ] + }, + "AccountLimits": { + "type": "object", + "properties": { + "can_create_projects": { + "type": "boolean" + }, + "can_pin_projects": { + "type": "boolean" + }, + "can_create_users": { + "type": "boolean" + }, + "can_upload_files": { + "type": "boolean" + } + } + }, + "AccountLogo": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + } + }, + "AccountSettings": { + "type": "object", + "properties": { + "company_hq_enabled": { + "type": "boolean" + }, + "teams_enabled": { + "type": "boolean" + }, + "projects_enabled": { + "type": "boolean" + } + } + }, + "AccountSubscription": { + "type": "object", + "properties": { + "short_name": { + "type": "string" + }, + "proper_name": { + "type": "string" + }, + "project_limit": { + "type": "integer", + "format": "int32" + }, + "teams": { + "type": "boolean" + }, + "clients": { + "type": "boolean" + }, + "templates": { + "type": "boolean" + }, + "logo": { + "type": "boolean" + }, + "timesheet": { + "type": "boolean" + } + } + }, + "BadRequestErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "BareFieldBadRequestErrorResponseContent": { + "$ref": "#/components/schemas/FieldErrorMap" + }, + "BareFieldValidationErrorResponseContent": { + "$ref": "#/components/schemas/FieldErrorMap" + }, + "Bookmark": { + "type": "object", + "description": "A personal bookmark: the current user's link to a single recording.\nThe wrapped recording is the shared recording projection, whose `parent`\nis optional (docked recordings and doors omit it).", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "recording": { + "$ref": "#/components/schemas/Recording" + } + }, + "required": [ + "created_at", + "id", + "recording", + "updated_at" + ] + }, + "BookmarkStatus": { + "type": "object", + "description": "The current user's bookmark state for one recording.", + "properties": { + "bookmarked": { + "type": "boolean" + } + }, + "required": [ + "bookmarked" + ] + }, + "Boost": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "content": { + "type": "string" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "booster": { + "$ref": "#/components/schemas/Person" + }, + "recording": { + "$ref": "#/components/schemas/RecordingParent" + } + }, + "required": [ + "created_at", + "id" + ] + }, + "BucketCardsGroup": { + "type": "object", + "description": "One project's slice of a filtered card listing: the parent project and the\nmatching cards (each carrying its steps).", + "properties": { + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "cards": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Card" + } + } + }, + "required": [ + "bucket", + "cards" + ] + }, + "BucketTodosGroup": { + "type": "object", + "description": "One project's slice of a filtered to-do listing: the parent project and the\nmatching to-dos (each carrying its steps).", + "properties": { + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "todos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + } + }, + "required": [ + "bucket", + "todos" + ] + }, + "Calendar": { + "type": "object", + "description": "A per-account calendar (wire type Calendar), keyed by its own bucket id.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "One of: white, red, orange, yellow, green, blue, aqua, purple, gray,\npink, brown." + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "schedule_url": { + "type": "string", + "description": "API URL of the calendar's underlying schedule resource." + } + }, + "required": [ + "app_url", + "color", + "created_at", + "id", + "name", + "schedule_url", + "type", + "updated_at", + "url" + ] + }, + "CalendarAttributes": { + "type": "object", + "description": "The writable calendar payload \u2014 the wire body is the nested\n{calendar: {color}} envelope.", + "properties": { + "color": { + "type": "string", + "description": "One of: white, red, orange, yellow, green, blue, aqua, purple, gray,\npink, brown." + } + }, + "required": [ + "color" + ] + }, + "Campfire": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "topic": { + "type": "string" + }, + "lines_url": { + "type": "string" + }, + "files_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CampfireLine": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "content": { + "type": "string" + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CampfireLineAttachment" + }, + "x-go-type-skip-optional-pointer": true + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CampfireLineAttachment": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "url": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "download_url": { + "type": "string", + "x-basecamp-auth-routable-url": {} + } + } + }, + "Card": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "nullable": true + }, + "completed": { + "type": "boolean" + }, + "completed_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "completion_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "completer": { + "$ref": "#/components/schemas/Person" + }, + "assignees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "completion_subscribers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "steps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardStep" + }, + "x-go-type-skip-optional-pointer": true + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "description_attachments", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CardColumn": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "cards_count": { + "type": "integer", + "format": "int32" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "cards_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "subscribers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "on_hold": { + "$ref": "#/components/schemas/CardColumnOnHold" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CardColumnOnHold": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "cards_count": { + "type": "integer", + "format": "int32" + }, + "cards_url": { + "type": "string" + } + }, + "required": [ + "cards_count", + "cards_url", + "created_at", + "id", + "inherits_status", + "status", + "title", + "updated_at" + ] + }, + "CardStep": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "completed": { + "type": "boolean" + }, + "completed_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "completer": { + "$ref": "#/components/schemas/Person" + }, + "assignees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "completion_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CardTable": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "subscribers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "lists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardColumn" + }, + "x-go-type-skip-optional-pointer": true + }, + "wormholes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Wormhole" + }, + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "Chatbot": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "service_name": { + "type": "string" + }, + "command_url": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "lines_url": { + "type": "string" + } + }, + "required": [ + "created_at", + "id", + "service_name", + "updated_at" + ] + }, + "ClientApproval": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "subject": { + "type": "string" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "replies_count": { + "type": "integer", + "format": "int32" + }, + "replies_url": { + "type": "string" + }, + "approval_status": { + "type": "string" + }, + "approver": { + "$ref": "#/components/schemas/Person" + }, + "responses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClientApprovalResponse" + }, + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "app_url", + "bucket", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "ClientApprovalResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "approved": { + "type": "boolean" + } + } + }, + "ClientCompany": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "ClientCorrespondence": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "subject": { + "type": "string" + }, + "replies_count": { + "type": "integer", + "format": "int32" + }, + "replies_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "subject", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "ClientReply": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + } + }, + "required": [ + "app_url", + "bucket", + "content", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "ClientSide": { + "type": "object", + "deprecated": true, + "description": "This shape is deprecated since 2024-01: Use Client Visibility feature instead", + "properties": { + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "x-deprecated-reason": "This shape is deprecated since 2024-01: Use Client Visibility feature instead" + }, + "CloudFile": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string", + "description": "The link on the external service \u2014 NOT this record's API URL. The\ncloud_files jbuilder renders the shared recording partial first and then\n`json.(recording.recordable, :url, :service)`, which overwrites the\nrecording's `url` key with the recordable's. `app_url` is still this\nrecord's Basecamp URL." + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "service": { + "$ref": "#/components/schemas/CloudFileService" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "description_attachments", + "id", + "inherits_status", + "parent", + "service", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CloudFileService": { + "type": "object", + "description": "The external service a cloud file points at, embedded in every cloud file\nresponse so clients can render the service's name, supporting text, and\nexample URL without shipping a hard-coded catalogue. Serialized by\nCloudFile::Service#as_json.", + "properties": { + "name": { + "type": "string" + }, + "example_url": { + "type": "string", + "description": "A representative URL for the service, suitable as an input placeholder." + }, + "code": { + "type": "string", + "description": "Short identifier for the external service \u2014 \"dropbox\", \"google_doc\",\n\"figma\", \"other\", \u2026 Derived from the CloudFile::Service subclass name, so it\nis always present. `other` accepts any well-formed HTTPS URL." + }, + "valid_patterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Regular expressions the cloud file's `url` is validated against. Sending a\n`url` that matches none of the selected service's patterns is a 422." + }, + "supporting_text": { + "type": "string", + "description": "Human-readable hint (\"a file or folder on Dropbox\"). Absent for services\nthat declare none \u2014 CloudFile::Service::Services::Other, for one." + } + }, + "required": [ + "code", + "example_url", + "name", + "valid_patterns" + ] + }, + "Comment": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "CreateAnswerResponseContent": { + "$ref": "#/components/schemas/QuestionAnswer" + }, + "CreateAttachmentInputPayload": { + "type": "string", + "contentEncoding": "byte" + }, + "CreateAttachmentResponseContent": { + "type": "object", + "properties": { + "attachable_sgid": { + "type": "string" + } + } + }, + "CreateBookmarkResponseContent": { + "$ref": "#/components/schemas/Bookmark" + }, + "CreateCampfireLineRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "content_type": { + "type": "string" + } + }, + "required": [ + "content" + ] + }, + "CreateCampfireLineResponseContent": { + "$ref": "#/components/schemas/CampfireLine" + }, + "CreateCampfireUploadInputPayload": { + "type": "string", + "description": "Raw binary content of the file. Set the Content-Type header to match\nthe file's media type (e.g. \"image/png\", \"application/pdf\").", + "contentEncoding": "byte" + }, + "CreateCampfireUploadResponseContent": { + "$ref": "#/components/schemas/CampfireLine" + }, + "CreateCardColumnRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "title" + ] + }, + "CreateCardColumnResponseContent": { + "$ref": "#/components/schemas/CardColumn" + }, + "CreateCardRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "notify": { + "type": "boolean" + } + }, + "required": [ + "title" + ] + }, + "CreateCardResponseContent": { + "$ref": "#/components/schemas/Card" + }, + "CreateCardStepRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "assignee_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "required": [ + "title" + ] + }, + "CreateCardStepResponseContent": { + "$ref": "#/components/schemas/CardStep" + }, + "CreateChatbotRequestContent": { + "type": "object", + "properties": { + "service_name": { + "type": "string" + }, + "command_url": { + "type": "string" + } + }, + "required": [ + "service_name" + ] + }, + "CreateChatbotResponseContent": { + "$ref": "#/components/schemas/Chatbot" + }, + "CreateCloudFileRequestContent": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "service": { + "type": "string", + "description": "Short identifier for the external service \u2014 \"dropbox\", \"google_doc\",\n\"figma\", \"other\", \u2026 Derived from the CloudFile::Service subclass name, so it\nis always present. `other` accepts any well-formed HTTPS URL." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "visible_to_clients": { + "type": "boolean", + "description": "Whether the cloud file is visible to the project's clients. Applies only\nwhen creating directly in the tool's vault \u2014 an item created inside a\nfolder inherits the folder's visibility and ignores this. A client caller\nalways creates client-visible records regardless of what is sent." + } + }, + "required": [ + "service", + "url" + ] + }, + "CreateCloudFileResponseContent": { + "$ref": "#/components/schemas/CloudFile" + }, + "CreateCommentRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string" + } + }, + "required": [ + "content" + ] + }, + "CreateCommentResponseContent": { + "$ref": "#/components/schemas/Comment" + }, + "CreateDocumentRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + }, + "status": { + "type": "string", + "description": "active|drafted" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "title" + ] + }, + "CreateDocumentResponseContent": { + "$ref": "#/components/schemas/Document" + }, + "CreateEventBoostRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string", + "maxLength": 16 + } + }, + "required": [ + "content" + ] + }, + "CreateEventBoostResponseContent": { + "$ref": "#/components/schemas/Boost" + }, + "CreateFolderRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The folder's name. Defaults to `New folder` when blank, null, or omitted." + }, + "project_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "IDs of the projects to file into the folder \u2014 the same ids the folder\nreports back as `bucket_ids` and expands as `projects`. This does not\nround-trip under its own name. Omit it, or send null or an empty array,\nfor an empty folder." + } + } + }, + "CreateFolderResponseContent": { + "$ref": "#/components/schemas/FolderWithProjects" + }, + "CreateGaugeNeedleRequestContent": { + "type": "object", + "properties": { + "gauge_needle": { + "$ref": "#/components/schemas/GaugeNeedlePayload" + }, + "notify": { + "type": "string", + "description": "Who to notify: \"everyone\", \"working_on\", \"custom\", or omit for nobody" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Array of people IDs to notify (only used when notify is \"custom\")" + } + }, + "required": [ + "gauge_needle" + ] + }, + "CreateGaugeNeedleResponseContent": { + "$ref": "#/components/schemas/GaugeNeedle" + }, + "CreateGoogleDocumentRequestContent": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "document_type": { + "type": "string", + "description": "One of \"doc\", \"sheet\", \"slide\", \"other\". Backed by a Rails enum, so an\nunrecognized value is rejected up front with a field-keyed 422\n({\"errors\": {\"document_type\": [\"is not a valid document type\"]}}) rather\nthan reaching validation." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "status": { + "type": "string", + "description": "active|drafted \u2014 defaults to drafted" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "visible_to_clients": { + "type": "boolean", + "description": "Whether the document is visible to the project's clients. Applies only\nwhen creating directly in the tool's vault \u2014 an item created inside a\nfolder inherits the folder's visibility and ignores this. A client caller\nalways creates client-visible records regardless of what is sent." + } + }, + "required": [ + "document_type", + "url" + ] + }, + "CreateGoogleDocumentResponseContent": { + "$ref": "#/components/schemas/GoogleDocument" + }, + "CreateLineupMarkerRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "date": { + "type": "string" + } + }, + "required": [ + "date", + "name" + ] + }, + "CreateMessageRequestContent": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "content": { + "type": "string" + }, + "status": { + "type": "string", + "description": "active|drafted" + }, + "category_id": { + "type": "integer", + "format": "int64" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "subject" + ] + }, + "CreateMessageResponseContent": { + "$ref": "#/components/schemas/Message" + }, + "CreateMessageTypeRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "icon": { + "type": "string" + } + }, + "required": [ + "icon", + "name" + ] + }, + "CreateMessageTypeResponseContent": { + "$ref": "#/components/schemas/MessageType" + }, + "CreatePersonRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "format": "password" + }, + "email_address": { + "type": "string", + "format": "password" + }, + "title": { + "type": "string", + "format": "password" + }, + "company_name": { + "type": "string", + "format": "password" + } + }, + "required": [ + "email_address", + "name" + ] + }, + "CreateProjectFromTemplateRequestContent": { + "type": "object", + "properties": { + "project": { + "$ref": "#/components/schemas/ProjectConstructionAttributes" + } + }, + "required": [ + "project" + ] + }, + "CreateProjectFromTemplateResponseContent": { + "$ref": "#/components/schemas/ProjectConstruction" + }, + "CreateProjectRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "CreateProjectResponseContent": { + "$ref": "#/components/schemas/Project" + }, + "CreateQuestionRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "schedule": { + "$ref": "#/components/schemas/QuestionSchedule" + }, + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "schedule", + "title" + ] + }, + "CreateQuestionResponseContent": { + "$ref": "#/components/schemas/Question" + }, + "CreateRecordingBoostRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string", + "maxLength": 16 + } + }, + "required": [ + "content" + ] + }, + "CreateRecordingBoostResponseContent": { + "$ref": "#/components/schemas/Boost" + }, + "CreateScheduleEntryRequestContent": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "starts_at": { + "type": "string", + "description": "The entry's start, as a bare date (\"2026-06-01\") for an all-day entry or a\nfull timestamp (\"2026-06-01T09:00:00Z\") otherwise \u2014 the same two forms the\nresponse renders, and the same two ReplaceScheduleEntry accepts.\n\nCreate and replace share one permit list:\n`Schedules::Entries::BaseController#base_schedule_entry_params` is what\nboth `new_schedule_entry_params` and `update_schedule_entry_params` call,\nand Schedule::Entry does no format-specific parsing of either bound, so\nwhatever one operation takes the other takes too.\n\nTreat the value as opaque and send it verbatim. Parsing it into a\ndate-time type and re-rendering rewrites an all-day entry's bounds into\nmidnight timestamps, which is why every SDK models it as a string." + }, + "ends_at": { + "type": "string", + "description": "The entry's end. See starts_at for the date-vs-timestamp rule." + }, + "description": { + "type": "string" + }, + "participant_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "all_day": { + "type": "boolean" + }, + "notify": { + "type": "boolean" + }, + "url": { + "type": "string", + "description": "The entry's join link \u2014 a video-call URL or similar, up to 2500\ncharacters, validated as a URL when present. A scheme-less value is\nnormalized to `https://`.\n\nSpell it `url` on the way in and read it back as `join_url`: the response\nkey `url` is the entry's own Basecamp API URL, written by a partial that\nrenders before this field, so BC3 emits the join link under a\nnon-colliding name. Sending `join_url` instead is silently dropped by\nstrong parameters \u2014 the create succeeds with no join link.\n\nAccepted on create since long before it was documented:\n`Schedules::Entries::BaseController#base_schedule_entry_params` permits it\nand `new_schedule_entry_params` passes it through unchanged for API\nrequests. Modeling it only on ReplaceScheduleEntry forced callers into a\nthree-request read-modify-write for a field the create already took \u2014 and\ncreate is the notifying write, so participants learned about a video call\nbefore its link existed." + }, + "highlighted": { + "type": "boolean", + "description": "Whether the entry is highlighted on the schedule. Defaults to false.\n\nDo not send an explicit null: `schedule_entries.highlighted` is NOT NULL,\nso BC3 raises rather than falling back to the default. Omit it instead \u2014\nevery SDK's request compactor already drops unset members." + }, + "status": { + "type": "string", + "description": "Publication state at creation \u2014 `active|drafted`, defaulting to `active`\nfor an API create.\n\nA top-level parameter, not part of the entry's attributes: `status` is a\nRecording column, so `wrap_parameters` leaves it outside the\n`schedule_entry` envelope and `Recording::StatusParam#status_param` reads\nit directly. On create it accepts `drafted`, `active`, `archived` or\n`trashed` and raises `ActionController::BadRequest` \u2014 a 400, not a 422 \u2014\nfor anything else; the two documented values are the two worth sending.\n\nUnlike messages and documents, schedule-entry drafts are not listed by\nGetMyDrafts." + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "ends_at", + "starts_at", + "summary" + ] + }, + "CreateScheduleEntryResponseContent": { + "$ref": "#/components/schemas/ScheduleEntry" + }, + "CreateTemplateRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "CreateTemplateResponseContent": { + "$ref": "#/components/schemas/Template" + }, + "CreateTimesheetEntryRequestContent": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "hours": { + "type": "string" + }, + "description": { + "type": "string" + }, + "person_id": { + "type": "integer", + "format": "int64" + } + }, + "required": [ + "date", + "hours" + ] + }, + "CreateTimesheetEntryResponseContent": { + "$ref": "#/components/schemas/TimesheetEntry" + }, + "CreateTodoRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "assignee_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "completion_subscriber_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "notify": { + "type": "boolean" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "starts_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + } + }, + "required": [ + "content" + ] + }, + "CreateTodoResponseContent": { + "$ref": "#/components/schemas/Todo" + }, + "CreateTodolistGroupRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "CreateTodolistGroupResponseContent": { + "$ref": "#/components/schemas/Todolist" + }, + "CreateTodolistRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "name" + ] + }, + "CreateTodolistResponseContent": { + "$ref": "#/components/schemas/Todolist" + }, + "CreateTodosetTodoRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "assignee_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "completion_subscriber_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "notify": { + "type": "boolean" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "starts_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + } + }, + "required": [ + "content" + ] + }, + "CreateTodosetTodoResponseContent": { + "$ref": "#/components/schemas/Todo" + }, + "CreateToolRequestContent": { + "type": "object", + "properties": { + "tool_type": { + "type": "string", + "description": "Tool type to add to the project dock. Values: Chat::Transcript|Inbox|Kanban::Board|Message::Board|Questionnaire|Schedule|Todoset|Vault." + }, + "title": { + "type": "string", + "description": "Title for the new tool. When omitted, Basecamp assigns the next available default title for the tool type." + }, + "visible_to_clients": { + "type": "boolean", + "description": "Create the tool already visible to clients. Honored only for tool types that manage their own client visibility (Chat::Transcript, Kanban::Board), which otherwise start hidden; every other tool type ignores it and inherits the project default." + } + }, + "required": [ + "tool_type" + ] + }, + "CreateToolResponseContent": { + "$ref": "#/components/schemas/Tool" + }, + "CreateUploadRequestContent": { + "type": "object", + "properties": { + "attachable_sgid": { + "type": "string" + }, + "description": { + "type": "string" + }, + "base_name": { + "type": "string" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "attachable_sgid" + ] + }, + "CreateUploadResponseContent": { + "$ref": "#/components/schemas/Upload" + }, + "CreateUploadVersionRequestContent": { + "type": "object", + "properties": { + "attachable_sgid": { + "type": "string" + }, + "base_name": { + "type": "string", + "description": "Omit to keep the uploaded file's own name. Sending \"\" also keeps it." + }, + "description": { + "type": "string", + "description": "Presence-aware: omit to carry the previous version's description forward,\nsend \"\" to clear it, send a value to set it." + }, + "notify": { + "type": "string", + "description": "Who to notify: \"default\", \"everyone\", or \"custom\" (the people in subscriptions).\n\nOmit both this and subscriptions to notify nobody. A subscriptions array sent\nwithout notify is read as \"custom\"." + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "People to notify about the replacement and subscribe to the upload." + } + }, + "required": [ + "attachable_sgid" + ] + }, + "CreateUploadVersionResponseContent": { + "$ref": "#/components/schemas/Upload" + }, + "CreateVaultRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + }, + "required": [ + "title" + ] + }, + "CreateVaultResponseContent": { + "$ref": "#/components/schemas/Vault" + }, + "CreateWebhookRequestContent": { + "type": "object", + "properties": { + "payload_url": { + "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "boolean" + } + }, + "required": [ + "payload_url", + "types" + ] + }, + "CreateWebhookResponseContent": { + "$ref": "#/components/schemas/Webhook" + }, + "CreateWormholeRequestContent": { + "type": "object", + "properties": { + "destination_recording_id": { + "type": "integer", + "description": "Id of the destination column (on another accessible card table) to link to.", + "format": "int64" + } + }, + "required": [ + "destination_recording_id" + ] + }, + "CreateWormholeResponseContent": { + "$ref": "#/components/schemas/Wormhole" + }, + "DisableCardColumnOnHoldResponseContent": { + "$ref": "#/components/schemas/CardColumn" + }, + "DockItem": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "name": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "enabled", + "id", + "name", + "title", + "url" + ] + }, + "Document": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "DoorService": { + "type": "object", + "description": "Metadata describing the recognized external service backing an external link\n(`Door` recording): its display name, a canonical example URL, a short code,\nthe URL patterns Basecamp recognizes for it, and human supporting text. `code`\nis `other` for a generic link.", + "properties": { + "name": { + "type": "string" + }, + "example_url": { + "type": "string" + }, + "code": { + "type": "string" + }, + "valid_patterns": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-type-skip-optional-pointer": true + }, + "supporting_text": { + "type": "string" + } + } + }, + "Draft": { + "type": "object", + "description": "A draft envelope: a message, document, upload, or client approval/\ncorrespondence saved but not yet published. Flat and purpose-built \u2014\nNOT the shared recording projection.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "app_url": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string", + "description": "Short recordable name: message, document, upload, client_approval,\nor client_correspondence." + }, + "bucket": { + "$ref": "#/components/schemas/DraftBucket" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/components/schemas/DraftParent" + }, + { + "type": "null" + } + ], + "x-go-type": "DraftParent" + }, + "excerpt": { + "type": "string", + "description": "Up to 300 characters of plain text; empty string when the draft has no body." + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "scheduled_posting_at": { + "type": [ + "string", + "null" + ], + "description": "Always present; `null` unless the draft is scheduled to publish later.\nRequired-presence with nullable value, like `parent`.", + "x-go-type": "time.Time" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "excerpt", + "id", + "parent", + "scheduled_posting_at", + "title", + "type", + "updated_at" + ] + }, + "DraftBucket": { + "type": "object", + "description": "The project a draft lives in (drafts-specific projection: id, name, app_url).", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "id", + "name" + ] + }, + "DraftParent": { + "type": "object", + "description": "The parent recording a draft is filed under (id, title, app_url).", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "id", + "title" + ] + }, + "EnableCardColumnOnHoldResponseContent": { + "$ref": "#/components/schemas/CardColumn" + }, + "EnableOutOfOfficeRequestContent": { + "type": "object", + "properties": { + "out_of_office": { + "$ref": "#/components/schemas/OutOfOfficePayload" + } + }, + "required": [ + "out_of_office" + ] + }, + "EnableOutOfOfficeResponseContent": { + "$ref": "#/components/schemas/OutOfOffice" + }, + "Event": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "recording_id": { + "type": "integer", + "format": "int64" + }, + "action": { + "type": "string" + }, + "details": { + "$ref": "#/components/schemas/EventDetails" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "action", + "created_at", + "creator", + "id", + "recording_id" + ] + }, + "EventDetails": { + "type": "object", + "properties": { + "added_person_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "x-go-type-skip-optional-pointer": true + }, + "removed_person_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "x-go-type-skip-optional-pointer": true + }, + "notified_recipient_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "EverythingFile": { + "type": "object", + "description": "A single item in the /files.json feed. An optional-field superset over three\nwire variants \u2014 a full Upload recording, a Basecamp Document recording, and a\nrich-text attachment wrapped in a recording envelope (distinguished by\n`attachable_sgid` and blob metadata). Every field is optional; a given\ninstance populates only the fields of the variant it represents. Unknown\nfields are ignored by every SDK decoder, so the superset need not enumerate\nevery field of the Upload/Document recordings.", + "properties": { + "id": { + "type": "integer", + "description": "Recording (Upload/Document) or attachment id.", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string", + "description": "\"Upload\", \"Document\", or \"Attachment\"." + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "attachable_sgid": { + "type": "string", + "description": "Present on the rich-text attachment variant: signed global id of the\nattachment (uploads/documents omit it)." + }, + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "filename": { + "type": "string" + }, + "download_url": { + "type": "string", + "x-basecamp-auth-routable-url": {} + }, + "app_download_url": { + "type": "string" + }, + "width": { + "type": "integer", + "description": "Pixel width; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "height": { + "type": "integer", + "description": "Pixel height; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "description": { + "type": "string", + "description": "Rich-text description (upload/document variants)." + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "x-go-type-skip-optional-pointer": true + }, + "content": { + "type": "string", + "description": "Rich-text body of the Document variant (uploads/attachments omit it)." + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "FieldErrorMap": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "propertyNames": { + "type": "string" + } + }, + "FieldKeyedErrors": { + "type": "object", + "description": "The field-keyed 422 body: {\"errors\": {\"color\": [\"is not a valid color\"]}}.", + "properties": { + "errors": { + "$ref": "#/components/schemas/FieldErrorMap" + } + }, + "required": [ + "errors" + ] + }, + "FieldValidationErrorResponseContent": { + "$ref": "#/components/schemas/FieldKeyedErrors" + }, + "FirstWeekDay": { + "type": "string", + "enum": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ] + }, + "Folder": { + "type": "object", + "description": "A folder as the list returns it: the base shape, without expanded projects.\n\nDeliberately distinct from FolderWithProjects. A single shape with an\noptional `projects` member would make every list item declare a field the\nlist response never populates.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "description": "Always the string `Stack` \u2014 the wire type kept its pre-rename name." + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "bucket_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "IDs of the projects filed into this folder. Same ids as `project_ids` on\ncreate, and the ids FolderWithProjects expands under `projects`." + }, + "is_emoji_only_name": { + "type": "boolean" + }, + "star_url": { + "type": "string" + }, + "gauges_url": { + "type": [ + "string", + "null" + ], + "description": "Gauges URL covering this folder's projects; always emitted, `null` when\nnone of them is gauged. `@required` models the presence \u2014 the nullability\nis layered on in the OpenAPI (smithy-build.json jsonAdd -> type:\n[\"string\",\"null\"]), so Go types it *string because the value is nullable,\nnot because the key is optional.", + "x-go-type": "string" + }, + "color": { + "type": [ + "string", + "null" + ], + "description": "The viewer's colour customization for this folder; always emitted, `null`\nwhen unset. Required-and-nullable, like gauges_url.", + "x-go-type": "string" + }, + "image_url": { + "type": [ + "string", + "null" + ], + "description": "The viewer's folder image; always emitted, `null` when unset. Read-only:\nthere is no image create or update in v1. Required-and-nullable, like\ngauges_url.", + "x-go-type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "bucket_ids", + "color", + "created_at", + "gauges_url", + "id", + "image_url", + "is_emoji_only_name", + "name", + "star_url", + "type", + "updated_at", + "url" + ] + }, + "FolderWithProjects": { + "type": "object", + "description": "One folder plus the projects grouped inside it, as get/create/update return it.\n\nThe `projects` entries are the shared project projection, minus the\n`bookmarked` flag that only the projects index adds.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "description": "Always the string `Stack` \u2014 the wire type kept its pre-rename name." + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "bucket_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "IDs of the projects filed into this folder \u2014 the same set `projects`\nexpands." + }, + "is_emoji_only_name": { + "type": "boolean" + }, + "star_url": { + "type": "string" + }, + "gauges_url": { + "type": [ + "string", + "null" + ], + "description": "Gauges URL covering this folder's projects; always emitted, `null` when\nnone of them is gauged. Required-and-nullable (see Folder.gauges_url).", + "x-go-type": "string" + }, + "color": { + "type": [ + "string", + "null" + ], + "description": "The viewer's colour customization for this folder; always emitted, `null`\nwhen unset. Required-and-nullable.", + "x-go-type": "string" + }, + "image_url": { + "type": [ + "string", + "null" + ], + "description": "The viewer's folder image; always emitted, `null` when unset. Read-only.\nRequired-and-nullable.", + "x-go-type": "string" + }, + "url": { + "type": "string" + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Project" + }, + "description": "The projects filed into this folder, expanded. Always emitted; empty for\nan empty folder." + } + }, + "required": [ + "bucket_ids", + "color", + "created_at", + "gauges_url", + "id", + "image_url", + "is_emoji_only_name", + "name", + "projects", + "star_url", + "type", + "updated_at", + "url" + ] + }, + "ForbiddenErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "Forward": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "subject": { + "type": "string" + }, + "from": { + "type": "string" + }, + "replies_count": { + "type": "integer", + "format": "int32" + }, + "replies_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "subject", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "ForwardReply": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "Gauge": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "Optional (no `@required`): the type-specific partial renders the\ncompanion array only when the gauge has needles (bc3 `if\ngauge.any_needles?`), so a needle-less gauge omits the key entirely.\nNon-nullable \u2014 never served as JSON `null`.", + "x-go-type-skip-optional-pointer": true + }, + "enabled": { + "type": "boolean" + }, + "last_needle_color": { + "type": "string" + }, + "last_needle_position": { + "type": "integer", + "format": "int32" + }, + "previous_needle_position": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "created_at", + "id", + "updated_at" + ] + }, + "GaugeNeedle": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "color": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "created_at", + "description_attachments", + "id", + "updated_at" + ] + }, + "GaugeNeedlePayload": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "description": "Position of the needle (0-100)", + "format": "int32" + }, + "color": { + "type": "string", + "description": "Status color: green (default), yellow, or red" + }, + "description": { + "type": "string", + "description": "Rich text (HTML) description of the progress update" + } + }, + "required": [ + "position" + ] + }, + "GaugeNeedleUpdatePayload": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Rich text (HTML) description" + } + } + }, + "GaugeTogglePayload": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "required": [ + "enabled" + ] + }, + "GetAccountResponseContent": { + "$ref": "#/components/schemas/Account" + }, + "GetAnswerResponseContent": { + "$ref": "#/components/schemas/QuestionAnswer" + }, + "GetAnswersByPersonResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionAnswer" + } + }, + "GetAssignedTodosResponseContent": { + "type": "object", + "properties": { + "person": { + "$ref": "#/components/schemas/Person" + }, + "grouped_by": { + "type": "string" + }, + "todos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "GetBookmarkResponseContent": { + "$ref": "#/components/schemas/BookmarkStatus" + }, + "GetBoostResponseContent": { + "$ref": "#/components/schemas/Boost" + }, + "GetBubbleUpsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + } + }, + "GetCalendarResponseContent": { + "$ref": "#/components/schemas/Calendar" + }, + "GetCampfireLineResponseContent": { + "$ref": "#/components/schemas/CampfireLine" + }, + "GetCampfireResponseContent": { + "$ref": "#/components/schemas/Campfire" + }, + "GetCardColumnResponseContent": { + "$ref": "#/components/schemas/CardColumn" + }, + "GetCardResponseContent": { + "$ref": "#/components/schemas/Card" + }, + "GetCardStepResponseContent": { + "$ref": "#/components/schemas/CardStep" + }, + "GetCardTableResponseContent": { + "$ref": "#/components/schemas/CardTable" + }, + "GetChatbotResponseContent": { + "$ref": "#/components/schemas/Chatbot" + }, + "GetClientApprovalResponseContent": { + "$ref": "#/components/schemas/ClientApproval" + }, + "GetClientCorrespondenceResponseContent": { + "$ref": "#/components/schemas/ClientCorrespondence" + }, + "GetClientReplyResponseContent": { + "$ref": "#/components/schemas/ClientReply" + }, + "GetCloudFileResponseContent": { + "$ref": "#/components/schemas/CloudFile" + }, + "GetCommentResponseContent": { + "$ref": "#/components/schemas/Comment" + }, + "GetDocumentResponseContent": { + "$ref": "#/components/schemas/Document" + }, + "GetEverythingCheckinsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingCommentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingCompletedCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketCardsGroup" + } + }, + "GetEverythingCompletedTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketTodosGroup" + } + }, + "GetEverythingFilesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EverythingFile" + } + }, + "GetEverythingForwardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingMessagesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingNoDueDateCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketCardsGroup" + } + }, + "GetEverythingNoDueDateTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketTodosGroup" + } + }, + "GetEverythingNotNowCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketCardsGroup" + } + }, + "GetEverythingOpenCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketCardsGroup" + } + }, + "GetEverythingOpenTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketTodosGroup" + } + }, + "GetEverythingOverdueCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Card" + } + }, + "GetEverythingOverdueTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + }, + "GetEverythingUnassignedCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketCardsGroup" + } + }, + "GetEverythingUnassignedTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BucketTodosGroup" + } + }, + "GetFolderResponseContent": { + "$ref": "#/components/schemas/FolderWithProjects" + }, + "GetForwardReplyResponseContent": { + "$ref": "#/components/schemas/ForwardReply" + }, + "GetForwardResponseContent": { + "$ref": "#/components/schemas/Forward" + }, + "GetGaugeNeedleResponseContent": { + "$ref": "#/components/schemas/GaugeNeedle" + }, + "GetGoogleDocumentResponseContent": { + "$ref": "#/components/schemas/GoogleDocument" + }, + "GetHillChartResponseContent": { + "$ref": "#/components/schemas/HillChart" + }, + "GetInboxResponseContent": { + "$ref": "#/components/schemas/Inbox" + }, + "GetMessageBoardResponseContent": { + "$ref": "#/components/schemas/MessageBoard" + }, + "GetMessageResponseContent": { + "$ref": "#/components/schemas/Message" + }, + "GetMessageTypeResponseContent": { + "$ref": "#/components/schemas/MessageType" + }, + "GetMyAssignmentsResponseContent": { + "type": "object", + "properties": { + "priorities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MyAssignment" + }, + "x-go-type-skip-optional-pointer": true + }, + "non_priorities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MyAssignment" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "GetMyCompletedAssignmentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MyAssignment" + } + }, + "GetMyDueAssignmentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MyAssignment" + } + }, + "GetMyNoteResponseContent": { + "$ref": "#/components/schemas/MyNote" + }, + "GetMyNotificationsResponseContent": { + "type": "object", + "properties": { + "unreads": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + }, + "x-go-type-skip-optional-pointer": true + }, + "reads": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + }, + "x-go-type-skip-optional-pointer": true + }, + "bubble_ups_count": { + "type": "integer", + "description": "Total number of current bubble-ups, for notification UI counts\n(independent of the `limit_bubble_ups` cap on the `bubble_ups` array).", + "format": "int32" + }, + "scheduled_bubble_ups_count": { + "type": "integer", + "description": "Total number of scheduled bubble-ups, for notification UI counts\n(present even when `limit_bubble_ups` omits the `scheduled_bubble_ups`\narray).", + "format": "int32" + }, + "memories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + }, + "description": "Legacy \"save forever\" collection. Permanently `[]` on BC5 by documented\ncontract (`doc/api/sections/my_notifications.md`, codified by BC3 #11628):\nan always-empty placeholder superseded by `bubble_ups`. BC4 (the `four`\nbranch) still populates it \u2014 an accepted BC4\u2192BC5 subtractive delta\nrecorded in `spec/api-gaps/memories-emptied-regression.md`. New\nintegrations should use `bubble_ups` / `scheduled_bubble_ups` and must\nnot rely on `memories` on BC5.", + "x-go-type-skip-optional-pointer": true + }, + "bubble_ups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + }, + "description": "Items the user has saved with Bubble Up (BC5 addition). Roughly the\nsuccessor to `memories` but with optional scheduling \u2014 see\n`scheduled_bubble_ups` for the time-deferred subset.", + "x-go-type-skip-optional-pointer": true + }, + "scheduled_bubble_ups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + }, + "description": "Bubble Ups scheduled to resurface in the future (BC5 addition).", + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "bubble_ups_count", + "scheduled_bubble_ups_count" + ] + }, + "GetMyPreferencesResponseContent": { + "$ref": "#/components/schemas/Preferences" + }, + "GetMyProfileResponseContent": { + "$ref": "#/components/schemas/Person" + }, + "GetOutOfOfficeResponseContent": { + "$ref": "#/components/schemas/OutOfOffice" + }, + "GetOverdueTodosResponseContent": { + "type": "object", + "properties": { + "under_a_week_late": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + }, + "x-go-type-skip-optional-pointer": true + }, + "over_a_week_late": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + }, + "x-go-type-skip-optional-pointer": true + }, + "over_a_month_late": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + }, + "x-go-type-skip-optional-pointer": true + }, + "over_three_months_late": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "GetPersonProgressResponseContent": { + "type": "object", + "properties": { + "person": { + "$ref": "#/components/schemas/Person" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineEvent" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "GetPersonResponseContent": { + "$ref": "#/components/schemas/Person" + }, + "GetProgressReportResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineEvent" + } + }, + "GetProjectConstructionResponseContent": { + "$ref": "#/components/schemas/ProjectConstruction" + }, + "GetProjectResponseContent": { + "$ref": "#/components/schemas/Project" + }, + "GetProjectTimelineResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineEvent" + } + }, + "GetProjectTimesheetResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimesheetEntry" + } + }, + "GetQuestionRemindersResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionReminder" + } + }, + "GetQuestionResponseContent": { + "$ref": "#/components/schemas/Question" + }, + "GetQuestionnaireResponseContent": { + "$ref": "#/components/schemas/Questionnaire" + }, + "GetRecordingTimesheetResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimesheetEntry" + } + }, + "GetScheduleEntryOccurrenceResponseContent": { + "$ref": "#/components/schemas/ScheduleEntry" + }, + "GetScheduleEntryResponseContent": { + "$ref": "#/components/schemas/ScheduleEntry" + }, + "GetScheduleResponseContent": { + "$ref": "#/components/schemas/Schedule" + }, + "GetSearchMetadataResponseContent": { + "$ref": "#/components/schemas/SearchMetadata" + }, + "GetSubscriptionResponseContent": { + "$ref": "#/components/schemas/Subscription" + }, + "GetTemplateResponseContent": { + "$ref": "#/components/schemas/Template" + }, + "GetTimesheetEntryResponseContent": { + "$ref": "#/components/schemas/TimesheetEntry" + }, + "GetTimesheetReportResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimesheetEntry" + } + }, + "GetTodoResponseContent": { + "$ref": "#/components/schemas/Todo" + }, + "GetTodolistOrGroupResponseContent": { + "$ref": "#/components/schemas/Todolist" + }, + "GetTodosetResponseContent": { + "$ref": "#/components/schemas/Todoset" + }, + "GetToolResponseContent": { + "$ref": "#/components/schemas/Tool" + }, + "GetUpcomingScheduleResponseContent": { + "type": "object", + "properties": { + "schedule_entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UpcomingScheduleEntry" + }, + "description": "Non-recurring entries starting in the window, chronological. Always\npresent \u2014 `reports/schedules/upcoming/index.json.jbuilder` writes all\nthree keys unconditionally, so an empty window is three empty arrays\nrather than a missing key." + }, + "recurring_schedule_entry_occurrences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UpcomingScheduleEntry" + }, + "description": "Realized occurrences of recurring entries falling in the window. Rendered\nthrough the same calendar entry partial as schedule_entries, so the two\narrays carry the same shape; an occurrence is distinguished only by its\n`recurring` flag being true. Always present." + }, + "assignables": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UpcomingAssignable" + }, + "description": "Dated to-dos, cards and steps falling in the window. Always present." + } + }, + "required": [ + "assignables", + "recurring_schedule_entry_occurrences", + "schedule_entries" + ] + }, + "GetUploadResponseContent": { + "$ref": "#/components/schemas/Upload" + }, + "GetVaultResponseContent": { + "$ref": "#/components/schemas/Vault" + }, + "GetWebhookResponseContent": { + "$ref": "#/components/schemas/Webhook" + }, + "GoogleDocument": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string", + "description": "The Google Workspace document link \u2014 NOT this record's API URL. Same\nrecordable-overwrites-recording rendering as CloudFile#url." + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "document_type": { + "type": "string", + "description": "One of \"doc\", \"sheet\", \"slide\", \"other\". Backed by a Rails enum, so an\nunrecognized value is rejected up front with a field-keyed 422\n({\"errors\": {\"document_type\": [\"is not a valid document type\"]}}) rather\nthan reaching validation." + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "description_attachments", + "document_type", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "HillChart": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "stale": { + "type": "boolean" + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "app_update_url": { + "type": "string" + }, + "app_versions_url": { + "type": "string" + }, + "dots": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HillChartDot" + }, + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "enabled", + "stale" + ] + }, + "HillChartDot": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "label": { + "type": "string" + }, + "color": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "color", + "id", + "label", + "position" + ] + }, + "Inbox": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "forwards_count": { + "type": "integer", + "format": "int32" + }, + "forwards_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "InternalServerErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "LineupMarker": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "date": { + "type": "string" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + } + }, + "required": [ + "created_at", + "date", + "id", + "name", + "updated_at" + ] + }, + "ListAnswersResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionAnswer" + } + }, + "ListAssignablePeopleResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + } + }, + "ListCampfireLinesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CampfireLine" + } + }, + "ListCampfireUploadsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CampfireLine" + } + }, + "ListCampfiresResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Campfire" + } + }, + "ListCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Card" + } + }, + "ListChatbotsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Chatbot" + } + }, + "ListClientApprovalsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClientApproval" + } + }, + "ListClientCorrespondencesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClientCorrespondence" + } + }, + "ListClientRepliesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ClientReply" + } + }, + "ListCommentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Comment" + } + }, + "ListDocumentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Document" + } + }, + "ListEventBoostsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Boost" + } + }, + "ListEventsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Event" + } + }, + "ListFoldersResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Folder" + } + }, + "ListForwardRepliesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ForwardReply" + } + }, + "ListForwardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Forward" + } + }, + "ListGaugeNeedlesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GaugeNeedle" + } + }, + "ListGaugesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Gauge" + } + }, + "ListLineupMarkersResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LineupMarker" + } + }, + "ListMessageTypesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageType" + } + }, + "ListMessagesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + } + }, + "ListMyBookmarksResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Bookmark" + } + }, + "ListMyDraftsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Draft" + } + }, + "ListPeopleResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + } + }, + "ListPingablePeopleResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + } + }, + "ListProjectPeopleResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + } + }, + "ListProjectsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Project" + } + }, + "ListQuestionAnswerersResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + } + }, + "ListQuestionsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Question" + } + }, + "ListRecordingBoostsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Boost" + } + }, + "ListRecordingsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "ListScheduleEntriesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleEntry" + } + }, + "ListTemplatesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Template" + } + }, + "ListTodolistGroupsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todolist" + } + }, + "ListTodolistsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todolist" + } + }, + "ListTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + }, + "ListUploadVersionsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UploadVersion" + } + }, + "ListUploadsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Upload" + } + }, + "ListVaultsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Vault" + } + }, + "ListWebhooksResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Webhook" + } + }, + "MarkAsReadRequestContent": { + "type": "object", + "properties": { + "readables": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of readable_sgid values identifying the items to mark as read" + } + }, + "required": [ + "readables" + ] + }, + "Message": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "subject": { + "type": "string" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "category": { + "$ref": "#/components/schemas/MessageType" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "subject", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "MessageBoard": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "messages_count": { + "type": "integer", + "format": "int32" + }, + "messages_url": { + "type": "string" + }, + "app_messages_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "MessageType": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + } + }, + "required": [ + "created_at", + "icon", + "id", + "name", + "updated_at" + ] + }, + "MoveCardColumnRequestContent": { + "type": "object", + "properties": { + "source_id": { + "type": "integer", + "format": "int64" + }, + "target_id": { + "type": "integer", + "format": "int64" + }, + "position": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "source_id", + "target_id" + ] + }, + "MoveCardRequestContent": { + "type": "object", + "properties": { + "column_id": { + "type": "integer", + "format": "int64" + }, + "position": { + "type": "integer", + "description": "1-indexed position within the destination column. Defaults to 1 (top).", + "format": "int32" + } + }, + "required": [ + "column_id" + ] + }, + "MyAssignment": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "app_url": { + "type": "string" + }, + "content": { + "type": "string" + }, + "starts_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "bucket": { + "$ref": "#/components/schemas/MyAssignmentBucket" + }, + "completed": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "assignees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MyAssignmentAssignee" + }, + "x-go-type-skip-optional-pointer": true + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "has_description": { + "type": "boolean" + }, + "priority_recording_id": { + "type": "integer", + "description": "Present on priority items", + "format": "int64" + }, + "parent": { + "$ref": "#/components/schemas/MyAssignmentParent" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MyAssignment" + }, + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "id" + ] + }, + "MyAssignmentAssignee": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "format": "password" + }, + "avatar_url": { + "type": "string", + "format": "password" + } + }, + "required": [ + "id" + ] + }, + "MyAssignmentBucket": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "MyAssignmentParent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "MyNote": { + "type": "object", + "description": "The per-user notebook note (wire type Notebook::Note). Before the first\nwrite, id/created_at/updated_at are present-but-null (required-nullable,\nlayered in the OpenAPI via jsonAdd) and content is empty.", + "properties": { + "id": { + "type": [ + "integer", + "null" + ], + "description": "Null until the note is first written.", + "format": "int64", + "x-go-type": "int64" + }, + "type": { + "type": "string" + }, + "created_at": { + "type": [ + "string", + "null" + ], + "description": "Null until the note is first written.", + "x-go-type": "time.Time" + }, + "updated_at": { + "type": [ + "string", + "null" + ], + "description": "Null until the note is first written.", + "x-go-type": "time.Time" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "content", + "content_attachments", + "created_at", + "id", + "type", + "updated_at", + "url" + ] + }, + "MyNoteAttributes": { + "type": "object", + "description": "The writable note payload \u2014 the wire body is the nested {note: {content}}\nenvelope, the ProjectConstructionAttributes treatment.", + "properties": { + "content": { + "type": "string", + "description": "The note's rich-text body (HTML)." + } + }, + "required": [ + "content" + ] + }, + "NotFoundErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "Notification": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "section": { + "type": "string", + "description": "The notification category: `inbox`, `chats`, `pings`, `bubbles`,\nor `mentions`." + }, + "unread_count": { + "type": "integer", + "format": "int32" + }, + "unread_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "read_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "readable_sgid": { + "type": "string" + }, + "readable_identifier": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content_excerpt": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "unread_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "memory_url": { + "type": "string" + }, + "bubble_up_url": { + "type": "string", + "description": "URL for the Bubble Up record covering this notification (BC5 addition).\nEligibility-gated \u2014 only present on items the current user can bubble up." + }, + "bubble_up_at": { + "type": "string", + "description": "Scheduled resurfacing time when this item is queued as a scheduled\nBubble Up (BC5 addition). Absent when there is no scheduled time.", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "subscription_url": { + "type": "string" + }, + "subscribed": { + "type": "boolean" + }, + "previewable_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PreviewableAttachment" + }, + "x-go-type-skip-optional-pointer": true + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "description": "Present on ping notifications", + "x-go-type-skip-optional-pointer": true + }, + "named": { + "type": "boolean", + "description": "Whether the ping has a custom name (pings only)" + }, + "image_url": { + "type": "string", + "description": "Custom image URL (pings only)" + } + }, + "required": [ + "created_at", + "id", + "updated_at" + ] + }, + "OutOfOffice": { + "type": "object", + "description": "When out of office is not enabled, `enabled` is `false` and\n`start_date`, `end_date`, and `back_on_date` are omitted.", + "properties": { + "person": { + "$ref": "#/components/schemas/OutOfOfficePerson" + }, + "enabled": { + "type": "boolean" + }, + "ongoing": { + "type": "boolean" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "back_on_date": { + "type": "string", + "description": "First working day after the out-of-office window ends.\nOmitted when out of office is not enabled." + } + } + }, + "OutOfOfficePayload": { + "type": "object", + "properties": { + "start_date": { + "type": "string", + "description": "Start date in ISO 8601 format (YYYY-MM-DD)" + }, + "end_date": { + "type": "string", + "description": "End date in ISO 8601 format (YYYY-MM-DD)" + } + }, + "required": [ + "end_date", + "start_date" + ] + }, + "OutOfOfficePerson": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "format": "password" + }, + "avatar_url": { + "type": "string", + "format": "password" + } + }, + "required": [ + "id" + ] + }, + "PauseQuestionResponseContent": { + "type": "object", + "properties": { + "paused": { + "type": "boolean" + } + } + }, + "Person": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "x-go-type": "types.FlexibleInt64", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "attachable_sgid": { + "type": "string" + }, + "name": { + "type": "string", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": true + } + }, + "email_address": { + "type": "string", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": true + } + }, + "personable_type": { + "type": "string" + }, + "title": { + "type": "string", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": false + } + }, + "bio": { + "type": "string", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": false + } + }, + "tagline": { + "type": "string", + "description": "Alias of `bio` introduced in BC5. BC3 emits both keys with identical content;\nolder BC4 responses may omit `tagline`. Prefer `bio` for cross-version reads.", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": false + } + }, + "location": { + "type": "string", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": false + } + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "admin": { + "type": "boolean" + }, + "owner": { + "type": "boolean" + }, + "client": { + "type": "boolean" + }, + "employee": { + "type": "boolean" + }, + "time_zone": { + "type": "string" + }, + "avatar_url": { + "type": "string", + "format": "password", + "x-basecamp-sensitive": { + "category": "pii", + "redact": true + } + }, + "company": { + "$ref": "#/components/schemas/PersonCompany" + }, + "can_manage_projects": { + "type": "boolean" + }, + "can_manage_people": { + "type": "boolean" + }, + "can_ping": { + "type": "boolean" + }, + "can_access_timesheet": { + "type": "boolean" + }, + "can_access_hill_charts": { + "type": "boolean" + } + }, + "required": [ + "id", + "name" + ] + }, + "PersonCompany": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "format": "password" + } + }, + "required": [ + "id", + "name" + ] + }, + "Preferences": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "time_zone_name": { + "type": "string", + "description": "Returned as a Rails-style name (e.g. \"Central Time (US & Canada)\")." + }, + "first_week_day": { + "type": "string" + }, + "time_format": { + "type": "string" + } + } + }, + "PreferencesPayload": { + "type": "object", + "properties": { + "time_zone_name": { + "type": "string", + "description": "Time zone name. Accepts any valid Rails time zone name (e.g.\n\"London\", \"UTC\") as well as IANA identifiers (e.g.\n\"America/Chicago\"), which are normalized to the matching\nRails-style name before saving." + }, + "first_week_day": { + "type": "string", + "description": "First day of the week: Sunday, Monday, Tuesday, etc." + }, + "time_format": { + "type": "string", + "description": "Time display format: twelve_hour or twenty_four_hour" + } + } + }, + "PreviewableAttachment": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "content_type": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "filesize": { + "type": "integer", + "format": "int64" + }, + "width": { + "type": "integer", + "format": "int32" + }, + "height": { + "type": "integer", + "format": "int32" + } + } + }, + "PrioritizeAssignmentRequestContent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The recording id to prioritize.", + "format": "int64" + } + }, + "required": [ + "id" + ] + }, + "Project": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string", + "description": "active|archived|trashed" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "clients_enabled": { + "type": "boolean" + }, + "bookmark_url": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "dock": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DockItem" + }, + "x-go-type-skip-optional-pointer": true + }, + "bookmarked": { + "type": "boolean" + }, + "client_company": { + "$ref": "#/components/schemas/ClientCompany" + }, + "clientside": { + "$ref": "#/components/schemas/ClientSide", + "deprecated": true, + "x-deprecated-reason": "This shape is deprecated since 2024-01: Use Client Visibility feature instead" + } + }, + "required": [ + "app_url", + "created_at", + "id", + "name", + "status", + "updated_at", + "url" + ] + }, + "ProjectAccessResult": { + "type": "object", + "properties": { + "granted": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "revoked": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + } + } + }, + "ProjectConstruction": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "url": { + "type": "string" + }, + "project": { + "$ref": "#/components/schemas/Project" + } + }, + "required": [ + "id", + "status" + ] + }, + "ProjectConstructionAttributes": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "ProjectLimitErrorResponseContent": { + "type": "object", + "description": "The account has reached its project limit. Raised by CreateProject and by\nUnarchiveProject, both of which add to the active project count.", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "Question": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "paused": { + "type": "boolean" + }, + "schedule": { + "$ref": "#/components/schemas/QuestionSchedule" + }, + "answers_count": { + "type": "integer", + "format": "int32" + }, + "answers_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "QuestionAnswer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "group_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "content", + "content_attachments", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "QuestionAnswerPayload": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "group_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + } + }, + "required": [ + "content" + ] + }, + "QuestionAnswerUpdatePayload": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "group_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + } + }, + "required": [ + "content" + ] + }, + "QuestionReminder": { + "type": "object", + "properties": { + "reminder_id": { + "type": "integer", + "format": "int64" + }, + "remind_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "group_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "question": { + "$ref": "#/components/schemas/Question" + } + } + }, + "QuestionSchedule": { + "type": "object", + "properties": { + "frequency": { + "type": "string" + }, + "days": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "hour": { + "type": "integer", + "format": "int32" + }, + "minute": { + "type": "integer", + "format": "int32" + }, + "week_instance": { + "type": "integer", + "format": "int32" + }, + "week_interval": { + "type": "integer", + "format": "int32" + }, + "month_interval": { + "type": "integer", + "format": "int32" + }, + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + } + } + }, + "Questionnaire": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "questions_url": { + "type": "string" + }, + "questions_count": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "name", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "RateLimitErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "error" + ] + }, + "Recording": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string", + "description": "API URL of the recording. Exception: in the `type=Door` (external-link)\nprojection, `url` is the door's **external destination address** (e.g. the\nFigma/Dropbox URL) and `app_url` is the Basecamp redirector \u2014 see the\ndoor-specific `service`/`description` fields below." + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "bubble_up_url": { + "type": "string", + "description": "URL of the Bubble Up record for this recording (BC5 addition). Optional\nhere because this is a polymorphic projection:\n`recordings/_recording.json.jbuilder` emits the key only when the caller\npasses `local_assigns[:bubbleupable]`, and `todolists/_todolist` is the\nonly partial that does. So a Todolist-shaped instance carries it and the\nother recording types do not." + }, + "content": { + "type": "string" + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "Rich-text companion arrays carried through the generic recording\nprojection (`to_recordable_partial_path` renders the full type-specific\npartial). A given recording is one type, so it carries only the array\nmatching its rich-text attribute (`content_attachments` for a\nComment/Message, `description_attachments` for a Todo/Card); a\nwebhook-sourced recording (base partial) carries neither. Optional (no\n`@required`), non-nullable.", + "x-go-type-skip-optional-pointer": true + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "See `content_attachments` \u2014 the description-attribute companion array.", + "x-go-type-skip-optional-pointer": true + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "description": "Boost count/URL. Carried on boostable recordings \u2014 notably the account-wide\naggregate feeds (`/messages.json`, `/comments.json`), whose type-specific\npartials render with `boostable: true`. Optional (absent on non-boostable\nrecordings and on the base/webhook partial).", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "subject": { + "type": "string", + "description": "Message subject. Present on `Message` recordings \u2014 notably the account-wide\n`/messages.json` aggregate feed, whose message partial renders `subject`." + }, + "category": { + "$ref": "#/components/schemas/RecordingCategory" + }, + "group_on": { + "type": "string", + "description": "Check-in grouping date (YYYY-MM-DD). Present on automatic check-in answer\n(`Question::Answer`) recordings \u2014 notably the `/checkins.json` aggregate\nfeed, whose answer partial renders `group_on`.", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "from": { + "type": "string", + "description": "Sender of an inbox forward. Present on `Inbox::Forward` recordings \u2014 notably\nthe `/forwards.json` aggregate feed, whose forward partial renders `from`." + }, + "replies_count": { + "type": "integer", + "description": "Reply count/URL for an inbox forward. Present on `Inbox::Forward`\nrecordings \u2014 notably the `/forwards.json` aggregate feed.", + "format": "int32" + }, + "replies_url": { + "type": "string" + }, + "position": { + "type": "integer", + "description": "Ordinal position within the project's External links section. Present on\n`Door` (external-link) recordings.", + "format": "int32" + }, + "description": { + "type": "string", + "description": "Rich-text (HTML) description shown beneath an external link. Present only\non `Door` recordings returned by the `type=Door` recordings query (the only\nendpoint that returns the full door shape). The external destination\naddress is `url` (not this field); `app_url` is the Basecamp redirector.\nSee `spec/api-gaps/external-links-doors.md`." + }, + "service": { + "$ref": "#/components/schemas/DoorService" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "RecordingBucket": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "RecordingCategory": { + "type": "object", + "description": "A message category (type) as rendered by the shared recordings/_category\npartial: id, display name, and icon. Present on categorized Message\nrecordings.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "icon": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "RecordingParent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + } + }, + "required": [ + "app_url", + "id", + "title", + "type", + "url" + ] + }, + "ReorderUpNextRequestContent": { + "type": "object", + "properties": { + "source_id": { + "type": "integer", + "description": "The recording id to move, chosen the same way as when prioritizing.", + "format": "int64" + }, + "position": { + "type": "integer", + "description": "The 1-based position to move it to.", + "format": "int32" + } + }, + "required": [ + "position", + "source_id" + ] + }, + "ReplaceDocumentRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + } + } + }, + "ReplaceDocumentResponseContent": { + "$ref": "#/components/schemas/Document" + }, + "ReplaceScheduleEntryRequestContent": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "starts_at": { + "type": "string", + "description": "The entry's start, as a bare date (\"2026-06-01\") for an all-day entry or a\nfull timestamp otherwise. Same rule as CreateScheduleEntry: send it\nverbatim, never parsed and re-rendered." + }, + "ends_at": { + "type": "string", + "description": "The entry's end. See starts_at for the date-vs-timestamp rule." + }, + "description": { + "type": "string" + }, + "participant_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Replaces the entry's participants.\n\nOmitting this member preserves the current participants; sending an empty\narray clears them. That guarantee is BC3-side and recent: until\nbasecamp/bc3#12425, `Schedules::EntriesController#update` called\n`replace_participants` unconditionally, so any update omitting the key \u2014\nincluding the shape in BC3's own \"Update a schedule entry\" doc example \u2014\nsilently removed every participant and notified each one. The controller\nnow guards on the request actually addressing participants." + }, + "all_day": { + "type": "boolean", + "description": "Whether the entry occupies whole days rather than a time range.\n\nNot carved out, and the carve-out list is what makes that dangerous to\nforget: `schedule_entries.all_day` is NOT NULL with a `false` default, so\nomitting this member on a replace resets it \u2014 silently converting an\nall-day entry into a midnight-to-midnight timed one. The SDK's merge-safe\nupdate and edit resend it from the read-back for exactly this reason.\n\nSending an explicit null is worse than omitting it: the column rejects\nNULL, so BC3 raises rather than falling back to the default. The same is\ntrue of highlighted." + }, + "notify": { + "type": "boolean" + }, + "url": { + "type": "string", + "description": "The entry's join link \u2014 a video-call URL or similar, up to 2500\ncharacters, validated as a URL when present.\n\nOmitting this member preserves the current join link; sending an empty\nstring clears it. Read it back as `join_url`, never as `url`: the entry's\n`url` is its own Basecamp API URL, written by a partial that renders\nbefore this one, so BC3 emits the join link under a non-colliding key.\nEchoing the response's `url` into this member would write the API URL into\nthe join link." + }, + "highlighted": { + "type": "boolean", + "description": "Whether the entry is highlighted on the schedule.\n\nOmitting this member preserves the current highlight; sending false\nremoves it. Preserved on omission because until basecamp/bc3#12502 the\nfield was writable but never returned, so no caller could resend it." + } + }, + "required": [ + "ends_at", + "starts_at" + ] + }, + "ReplaceScheduleEntryResponseContent": { + "$ref": "#/components/schemas/ScheduleEntry" + }, + "ReplaceTodoRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "assignee_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "completion_subscriber_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "notify": { + "type": "boolean" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "starts_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + } + }, + "required": [ + "content" + ] + }, + "ReplaceTodoResponseContent": { + "$ref": "#/components/schemas/Todo" + }, + "RepositionCardStepRequestContent": { + "type": "object", + "properties": { + "source_id": { + "type": "integer", + "format": "int64" + }, + "position": { + "type": "integer", + "description": "0-indexed position", + "format": "int32" + } + }, + "required": [ + "position", + "source_id" + ] + }, + "RepositionTodoRequestContent": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "format": "int32" + }, + "parent_id": { + "type": "integer", + "description": "Optional todolist ID to move the todo to a different parent", + "format": "int64" + } + }, + "required": [ + "position" + ] + }, + "RepositionTodolistGroupRequestContent": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "position" + ] + }, + "RepositionTodolistRequestContent": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "position" + ] + }, + "RepositionToolRequestContent": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "position" + ] + }, + "ResumeQuestionResponseContent": { + "type": "object", + "properties": { + "paused": { + "type": "boolean" + } + } + }, + "RichTextAttachment": { + "type": "object", + "description": "Structured metadata for a downloadable file attachment embedded in a\nrich text attribute. Every rich text attribute in an API response is\naccompanied by a corresponding `*_attachments` array named after the\nattribute (a Todo's `description_attachments` for its `description`).\nMentions, remote images, and opengraph embeds are excluded \u2014 only\ndownloadable file attachments appear.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "sgid": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "download_url": { + "type": "string", + "x-basecamp-auth-routable-url": {} + }, + "width": { + "type": "integer", + "description": "Pixel dimensions, present as keys on every attachment but null for\nnon-image blobs, and the BC3 API may serialize them float-spelled\n(`1024.0`) \u2014 hence optional/nullable rather than `@required` (the enhance\npass marks them `nullable: true` in the OpenAPI). All SDKs decode both\nforms faithfully and type the nullable value statically: Go `types.FlexInt`\n\u2192 `*int32`, Kotlin `Int?` via `FlexibleIntSerializer`, Swift `Int32?`,\nTypeScript `number | null`, Python `Optional[int | float]` (raw JSON keeps\nthe float), Ruby nilable. See SPEC.md \u00a710 Type Fidelity.", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "height": { + "type": "integer", + "description": "See `width` \u2014 same nullable/float-spelled behavior and cross-SDK note.", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "previewable": { + "type": "boolean" + }, + "preview_url": { + "type": "string" + }, + "thumbnail_url": { + "type": "string" + } + }, + "required": [ + "byte_size", + "content_type", + "download_url", + "filename", + "id", + "preview_url", + "previewable", + "sgid", + "thumbnail_url" + ] + }, + "Schedule": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "include_due_assignments": { + "type": "boolean" + }, + "entries_count": { + "type": "integer", + "format": "int32" + }, + "entries_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "ScheduleAttributes": { + "type": "object", + "properties": { + "start_date": { + "type": "string" + }, + "end_date": { + "type": "string" + } + } + }, + "ScheduleEntry": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "all_day": { + "type": "boolean", + "description": "Always sent. schedule_entries.all_day is NOT NULL with a false default,\nand every partial that renders an entry emits it." + }, + "starts_at": { + "type": "string", + "description": "Always sent, and a date rather than a timestamp for an all-day entry:\nBC3 renders starts_at_date_or_time, which is `starts_at.to_date` unless\nthe entry is timed, so an all-day entry reads back as `2016-06-01` and a\ntimed one as a full timestamp. The sibling all_day field discriminates.\n\nISO8601Timestamp is a plain string in this model, so both shapes decode;\ntreat the value as opaque and round-trip it verbatim rather than parsing\nit into a date type and re-rendering, which would rewrite an all-day\nentry's bounds.", + "x-go-type": "types.FlexibleTime", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "ends_at": { + "type": "string", + "description": "Always sent. See starts_at for the date-vs-timestamp rendering.", + "x-go-type": "types.FlexibleTime", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "join_url": { + "type": "string", + "description": "The entry's join link, or null when it has none.\n\nSent under this key rather than `url`, which is the entry's own Basecamp\nAPI URL: recordings/_recording writes that key first and the entry partial\nrenders after it. Write it back through the `url` member of\nReplaceScheduleEntry \u2014 the two spellings are deliberate, not a typo.\n\nOptional is now UNDER-modelled, and deliberately left that way for one\nrelease. Every operation that still returns this structure \u2014\nGetScheduleEntry, GetScheduleEntryOccurrence, ListScheduleEntries,\nCreateScheduleEntry, ReplaceScheduleEntry \u2014 renders\nschedules/entries/_entry, which emits this key unconditionally, so it is\nreally required-and-nullable.\n\nIt was optional because this structure used to cover two wire shapes: the\nreduced schedules/calendar/_entry, which omits it, reached here through\nGetUpcomingSchedule. That is no longer true \u2014 the report has its own\nUpcomingScheduleEntry projection (#635) \u2014 so the reason is retired and\nonly the cost of tightening remains. Promoting it is a separate contract\nchange: it forces every inline schedule-entry stub across six SDKs to\ncarry the key, and pairs with a required-and-nullable jsonAdd, so it\nbelongs in its own reviewable diff rather than riding this one." + }, + "highlighted": { + "type": "boolean", + "description": "Whether the entry is highlighted on the schedule.\n\nUnder-modelled for the same reason as join_url, and more plainly so: this\none is not even nullable (`schedule_entries.highlighted` is NOT NULL with\na false default), so the entry partial's unconditional emission makes it\nstraightforwardly @required. Tracked with join_url." + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "all_day", + "app_url", + "bucket", + "created_at", + "creator", + "description_attachments", + "ends_at", + "id", + "inherits_status", + "parent", + "starts_at", + "status", + "summary", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "SearchMetadata": { + "type": "object", + "properties": { + "recording_search_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchType" + } + }, + "file_search_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchType" + } + }, + "default_creator_label": { + "type": "string" + }, + "default_bucket_label": { + "type": "string" + }, + "default_circle_label": { + "type": "string" + }, + "default_file_type_label": { + "type": "string" + }, + "default_type_label": { + "type": "string" + } + }, + "required": [ + "default_bucket_label", + "default_circle_label", + "default_creator_label", + "default_file_type_label", + "default_type_label", + "file_search_types", + "recording_search_types" + ] + }, + "SearchResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchResult" + } + }, + "SearchResult": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "bubble_up_url": { + "type": "string", + "description": "URL of the Bubble Up record for this recording (BC5 addition). Optional\nhere because this is a polymorphic projection:\n`recordings/_recording.json.jbuilder` emits the key only when the caller\npasses `local_assigns[:bubbleupable]`, and `todolists/_todolist` is the\nonly partial that does. So a Todolist-shaped instance carries it and the\nother recording types do not." + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "content": { + "type": [ + "string", + "null" + ], + "description": "Always present, always null. `api/searches/show.json.jbuilder` renders the\nrecording's own partial and then unconditionally overwrites `content` with\n`nil` to strip the large HTML body out of the search payload. The key is\ntherefore guaranteed present on every result \u2014 required \u2014 and its value is\nguaranteed null. Read `plain_text_content` instead.", + "x-go-type": "string" + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Always present, always null \u2014 the description-attribute counterpart to\n`content`. Read `plain_text_description` instead.", + "x-go-type": "string" + }, + "plain_text_content": { + "type": "string", + "description": "A highlighted, truncated excerpt of the recording's content \u2014 **not** plain\ntext despite the name. `excerpt_and_highlight_matches` converts the rich\ntext with `to_plain_text`, escapes it with `html_escape_once`, then wraps\neach query match in `\u2026` and\ntruncates the result to 300 characters. Treat it as an HTML fragment.\n\nOptional and non-nullable: emitted only when the underlying recordable\nresponds to `content`, so a result whose type has no content attribute\nomits the key entirely rather than sending null." + }, + "plain_text_description": { + "type": "string", + "description": "The description-attribute counterpart to `plain_text_content`, with the\nsame highlighting, escaping, and 300-character truncation. Optional and\nnon-nullable \u2014 omitted when the recordable has no description attribute." + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "Rich-text companion arrays carried through the polymorphic search\nprojection. A given result is one recording type, so it carries only\nthe array matching its rich-text attribute (`content_attachments` for a\nComment/Message, `description_attachments` for a Todo); a webhook-sourced\nresult carries neither. Optional (no `@required`), non-nullable.\n\nSearch results additionally repeat this same array under a generic\n`attachments` key. It is a redundant projection, not a distinct\naggregate: `searches/show.json.jbuilder` emits\n`recording.downloadable_attachments`, which delegates to the recordable's\nsole `rich_text_content`, through the same `attachments/_attachment`\npartial that `recordings/_rich_text.json.jbuilder` uses to build the\ncompanion array. `RichText.rich_text_attribute` permits exactly one\nrich-text attribute per model, so the two keys always carry identical\nelements. Modeling `attachments` would duplicate the field, so it is\ndeliberately not modeled.", + "x-go-type-skip-optional-pointer": true + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "See `content_attachments` \u2014 the description-attribute companion array.", + "x-go-type-skip-optional-pointer": true + }, + "subject": { + "type": "string" + } + }, + "required": [ + "app_url", + "content", + "description", + "id", + "title", + "type", + "url" + ] + }, + "SearchType": { + "type": "object", + "description": "A selectable search filter option. `key` is the value passed back as a\nfilter parameter (null represents the default \"everything\" option); `value`\nis the human-readable label.", + "properties": { + "key": { + "type": [ + "string", + "null" + ], + "description": "Always present on the wire; `null` for the default \"everything\" option.\n`@required` models the presence; nullability of the value is layered on in\nthe OpenAPI (smithy-build.json jsonAdd -> type: [\"string\", \"null\"]) since\nSmithy has no native required-and-nullable.", + "x-go-type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ] + }, + "SetCardColumnColorRequestContent": { + "type": "object", + "properties": { + "color": { + "type": "string", + "description": "Valid colors: white, red, orange, yellow, green, blue, aqua, purple, gray, pink, brown" + } + }, + "required": [ + "color" + ] + }, + "SetCardColumnColorResponseContent": { + "$ref": "#/components/schemas/CardColumn" + }, + "SetCardStepCompletionRequestContent": { + "type": "object", + "properties": { + "completion": { + "type": "string", + "description": "Set to \"on\" to complete the step, \"\" (empty) to uncomplete" + } + }, + "required": [ + "completion" + ] + }, + "SetCardStepCompletionResponseContent": { + "$ref": "#/components/schemas/CardStep" + }, + "SetClientVisibilityRequestContent": { + "type": "object", + "properties": { + "visible_to_clients": { + "type": "boolean" + } + }, + "required": [ + "visible_to_clients" + ] + }, + "SetClientVisibilityResponseContent": { + "$ref": "#/components/schemas/Recording" + }, + "StorageLimitErrorResponseContent": { + "type": "object", + "description": "The account has reached its file storage limit.\n\nRaised by ResourceLimits#ensure_account_can_upload_files ahead of any operation\nthat stores new bytes. No retry can satisfy it: the account needs more storage,\nso this maps to `limit_exceeded` rather than a retryable server error.", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "SubscribeResponseContent": { + "$ref": "#/components/schemas/Subscription" + }, + "Subscription": { + "type": "object", + "properties": { + "subscribed": { + "type": "boolean" + }, + "count": { + "type": "integer", + "format": "int32" + }, + "url": { + "type": "string" + }, + "subscribers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "count", + "subscribed", + "url" + ] + }, + "Template": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "dock": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DockItem" + }, + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "created_at", + "id", + "name", + "updated_at" + ] + }, + "TimelineAttachment": { + "type": "object", + "description": "A single timeline-event attachment. This is an optional-field superset over\ntwo wire variants \u2014 a full Upload recording (upload-kind recordings, rendered\nby BC3's uploads/_upload partial: the complete recording projection + rich-\ntext description + the upload body) and a rich-text attachment/blob partial\n(all other recordings) \u2014 so one element type decodes either. Every field is\noptional; a given instance populates only the fields of the variant it\nrepresents. The upload-recording variant enumerates the full documented\nprojection so no documented field is silently dropped on decode.", + "properties": { + "id": { + "type": "integer", + "description": "Attachment or upload-recording id.", + "format": "int64" + }, + "content_type": { + "type": "string", + "description": "MIME type of the file." + }, + "byte_size": { + "type": "integer", + "description": "Size of the file in bytes.", + "format": "int64" + }, + "filename": { + "type": "string", + "description": "Original filename." + }, + "download_url": { + "type": "string", + "description": "Authenticated download URL for the file.", + "x-basecamp-auth-routable-url": {} + }, + "width": { + "type": "integer", + "description": "Pixel width; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "height": { + "type": "integer", + "description": "Pixel height; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "type": { + "type": "string", + "description": "Recording type, e.g. \"Upload\" (upload-recording variant)." + }, + "title": { + "type": "string", + "description": "Title of the upload recording." + }, + "status": { + "type": "string", + "description": "Publication status of the upload recording (e.g. \"active\")." + }, + "inherits_status": { + "type": "boolean", + "description": "Whether the recording inherits its status from its parent." + }, + "created_at": { + "type": "string", + "description": "When the upload recording was created.", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "description": "When the upload recording was last updated.", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "url": { + "type": "string", + "description": "API URL of the upload recording." + }, + "app_url": { + "type": "string", + "description": "Web URL of the upload recording." + }, + "bookmark_url": { + "type": "string", + "description": "Personal bookmark toggle URL for the current user." + }, + "subscription_url": { + "type": "string", + "description": "Subscription URL; present only when the recording is subscribable." + }, + "comments_count": { + "type": "integer", + "description": "Number of comments on the recording.", + "format": "int32" + }, + "comments_url": { + "type": "string", + "description": "API URL for the recording's comments." + }, + "boosts_count": { + "type": "integer", + "description": "Number of boosts on the recording.", + "format": "int32" + }, + "boosts_url": { + "type": "string", + "description": "API URL for the recording's boosts." + }, + "position": { + "type": "integer", + "description": "Position within its parent; present only when the recording is positioned.", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string", + "description": "Rich-text description of the upload (HTML), when present." + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "Rich-text attachments referenced by the description.", + "x-go-type-skip-optional-pointer": true + }, + "app_download_url": { + "type": "string", + "description": "Web download URL (upload-recording variant)." + }, + "visible_to_clients": { + "type": "boolean", + "description": "Whether the upload recording is visible to clients." + }, + "attachable_sgid": { + "type": "string", + "description": "Signed global id of the attachable (attachment variant)." + }, + "sgid": { + "type": "string", + "description": "Signed global id of the attachment (attachment variant)." + }, + "status_url": { + "type": "string", + "description": "URL to poll attachment processing status (attachment variant)." + }, + "caption": { + "type": "string", + "description": "Caption text, if any (attachment variant)." + }, + "key": { + "type": "string", + "description": "Storage key of the underlying blob (attachment variant)." + }, + "previewable": { + "type": "boolean", + "description": "Whether the blob can be previewed (attachment variant)." + }, + "preview_url": { + "type": "string", + "description": "Full-size preview URL (attachment variant)." + }, + "thumbnail_url": { + "type": "string", + "description": "Thumbnail preview URL (attachment variant)." + } + } + }, + "TimelineEvent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "kind": { + "type": "string", + "description": "What kind of activity the event records. Open, non-exhaustive vocabulary \u2014\nBC3 documents \"common values include\" and adds new kinds over time, so\ntreat unrecognized values as valid. Common values include message_created,\ncomment_created, todo_created, todo_completed, upload_created,\ndocument_created, google_document_created, schedule_entry_created,\nschedule_entry_rescheduled, question_created, question_answer_created,\nchat_transcript_rollup, kanban_card_created, kanban_card_completed,\ninbox_forward_created, client_correspondence_created, dock_created, and\nproject_access_changed." + }, + "parent_recording_id": { + "type": "integer", + "format": "int64" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "action": { + "type": "string" + }, + "target": { + "type": "string" + }, + "title": { + "type": "string" + }, + "summary_excerpt": { + "type": "string" + }, + "avatars_sample": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Avatar URLs of participants \u2014 populated for chat_transcript_rollup events\n(the people summarized in the rollup); an empty array otherwise.", + "x-go-type-skip-optional-pointer": true + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "data": { + "$ref": "#/components/schemas/TimelineEventData" + }, + "attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimelineAttachment" + }, + "description": "Files attached to the event's recording, when it has any. Heterogeneous:\nan upload-kind recording contributes its full Upload shape, while other\nrecordings contribute rich-text attachment/blob partials. Modeled as an\noptional-field superset so a single element type decodes either variant;\nconsumers should treat the per-variant fields as present-or-absent.", + "x-go-type-skip-optional-pointer": true + } + } + }, + "TimelineEventData": { + "type": "object", + "description": "Schedule-entry timing carried on schedule_entry_* timeline events. starts_at\nand ends_at are date-or-timestamp: a full ISO 8601 timestamp for timed\nentries, or a bare date (YYYY-MM-DD) when all_day is true. Modeled as\nISO8601Timestamp (mirroring ScheduleEntry), with the Go enhancement pass\nmapping them to types.FlexibleTime so date-only values decode; the other\nSDKs type them as plain strings.", + "properties": { + "all_day": { + "type": "boolean", + "description": "Whether the entry is all-day. BC3 emits all three members unconditionally\nwhenever the data object is present (schedule_entry_* events), so they are\nrequired within this struct." + }, + "starts_at": { + "type": [ + "string", + "null" + ], + "x-go-type": "types.FlexibleTime", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "ends_at": { + "type": [ + "string", + "null" + ], + "x-go-type": "types.FlexibleTime", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + } + }, + "required": [ + "all_day", + "ends_at", + "starts_at" + ] + }, + "TimesheetEntry": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "date": { + "type": "string" + }, + "description": { + "type": "string" + }, + "hours": { + "type": "string" + }, + "person": { + "$ref": "#/components/schemas/Person" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "Todo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string", + "description": "active|archived|trashed" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/TodoParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string" + }, + "completed": { + "type": "boolean" + }, + "content": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "starts_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "nullable": true + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "nullable": true + }, + "assignees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "completion_subscribers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Person" + }, + "x-go-type-skip-optional-pointer": true + }, + "completion_url": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardStep" + }, + "description": "Steps embedded in the Todo response (BC5 addition). The shared\n`steps/step` jbuilder partial emits the same shape as `CardStep`,\nso the existing `CardStepList` is reused.", + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "app_url", + "bucket", + "content", + "created_at", + "creator", + "description_attachments", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "TodoBucket": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "type" + ] + }, + "TodoParent": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "id", + "title", + "type", + "url" + ] + }, + "Todolist": { + "type": "object", + "description": "A to-do list, or a group inside one. There is only this shape.\n\nBC3 has no group model: a \"group\" is a `Todolist` whose parent is another\n`Todolist` (`Todolist.group?`), there is no `Todolist::Group` class, and\n`todolists/groups/index.json.jbuilder` and `show.json.jbuilder` both render\n`todolists/_todolist.json.jbuilder` \u2014 the same partial the list routes use.\nSo a group reports `\"type\": \"Todolist\"` (the shared recording partial emits\n`recordable_type`) and carries `description`/`description_attachments` like\nany other list. Every operation that returns a list or a group returns this\nstructure: the polymorphic GET/PUT, the todoset-scoped list, and the\ngroup list, group create and group get.\n\nDiscriminate STRUCTURALLY, never on `type` \u2014 which reads `\"Todolist\"` for\nboth variants. `_todolist.json.jbuilder` branches on `recording.parent.todoset?`\nand emits exactly one of:\n\n- `groups_url` \u2014 a to-do list; its `parent` is a Todoset.\n- `group_position_url` \u2014 a group; its `parent` is a Todolist.\n\nThe two are mutually exclusive and exactly one is always present.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string", + "description": "active|archived|trashed" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "bubble_up_url": { + "type": "string", + "description": "URL of the Bubble Up record for this recording (BC5 addition). Required:\n`todolists/_todolist.json.jbuilder` renders the shared recording partial\nwith `bubbleupable: true` unconditionally, and every list, show, and group\npath renders that partial \u2014 so the key is present on every projection of\nthis shape." + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/TodoParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string", + "description": "Rich text description (HTML). Required and never null: the shared rich-text\npartial emits the key unconditionally, and `format_api_content` funnels a\nblank or absent rich text through `call_pipeline`, which returns `\"\"` rather\nthan nil. A list with no description carries `\"\"`, not `null`, and a group\ncarries it too \u2014 `todolists/groups/{index,show}.json.jbuilder` render the\nsame partial." + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + }, + "description": "Downloadable files embedded in `description`. Required and never null:\nthe partial emits `rich_text&.downloadable_attachments.to_a`, so the value\nis `[]` when there is no description and when there are no attachments." + }, + "completed": { + "type": "boolean" + }, + "completed_ratio": { + "type": "string" + }, + "name": { + "type": "string" + }, + "todos_url": { + "type": "string" + }, + "groups_url": { + "type": "string", + "description": "API URL for this list's groups. The to-do-list half of the structural\ndiscriminator: emitted only when `parent` is a Todoset, and mutually\nexclusive with `group_position_url`." + }, + "group_position_url": { + "type": "string", + "description": "API URL for repositioning this group within its parent list. The group half\nof the structural discriminator: emitted only when `parent` is a Todolist,\nand mutually exclusive with `groups_url`." + }, + "app_todos_url": { + "type": "string", + "description": "In-app (non-API) URL for this record's to-dos, alongside the API-host\n`todos_url`." + }, + "color": { + "type": [ + "string", + "null" + ], + "description": "Color of the list or group, one of BC3's `Colored` enum values\n(`white red orange yellow green blue aqua purple gray pink brown`). For a\ntop-level list a hill-chart dot color takes precedence over the recording's\nown.\n\n**Required and nullable.** The key is always present and the value is often\n`null`. `_todolist.json.jbuilder` calls `json.color` in both branches of its\n`todolist_group?` conditional, so no projection of this shape omits it;\n`recordings.color` is a nullable integer column, so the value is JSON `null`\nwhenever it is unset \u2014 which, for a group, is the ordinary case.\n\nSmithy expresses neither half natively here, so both are layered onto the\nOpenAPI projection through `jsonAdd` in `spec/smithy-build.json`: the\n`[\"string\", \"null\"]` union for the value, the same treatment\n`SearchResult.content` and `SearchType.key` get, and an append to\n`Todolist.required` for the presence.\n\nThe member stays natively optional because `@required` collides with this\nshape's `@examples` (`GetTodolistOrGroup`): Smithy cannot express a `null`\nin an example for a `String` shape, so requiring the member would force the\ngroup example to advertise a color that uncolored groups do not have \u2014\ntrading a faithful example for a type the projection can state anyway. The\nsibling shapes that carry the same field (`Wormhole`, `Folder`,\n`FolderWithProjects`) are natively `@required` because none of them carries\nexamples. Nullability is not the weaker claim about presence: absence and\n`null` are different wire facts, and BC3 only ever sends the second.", + "x-go-type": "string" + }, + "comments_app_url": { + "type": "string", + "description": "In-app (non-API) URL for this recording's comments, alongside the API-host\n`comments_url`. Required and never null: `_todolist.json.jbuilder` emits\n`json.comments_app_url` unconditionally, from the\n`bucket_recording_comments_url` route helper \u2014 which returns a String or\nraises, and has no nil path. Native `@required` is enough here precisely\nbecause the value is never null, so it costs the examples a real URL rather\nthan a fiction." + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bubble_up_url", + "bucket", + "comments_app_url", + "created_at", + "creator", + "description", + "description_attachments", + "id", + "inherits_status", + "name", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients", + "color" + ] + }, + "Todoset": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "name": { + "type": "string" + }, + "todolists_count": { + "type": "integer", + "format": "int32" + }, + "todolists_url": { + "type": "string" + }, + "completed_ratio": { + "type": "string" + }, + "completed": { + "type": "boolean" + }, + "app_todolists_url": { + "type": "string" + }, + "todos_count": { + "type": "integer", + "description": "Total count of todos across all todolists in this todoset (BC5 addition).", + "format": "int32" + }, + "completed_loose_todos_count": { + "type": "integer", + "description": "Count of completed loose todos at the todoset level (BC5 addition).", + "format": "int32" + }, + "todos_url": { + "type": "string", + "description": "API URL for listing todos directly under this todoset (BC5 addition)." + }, + "app_todos_url": { + "type": "string", + "description": "In-app URL for viewing the todoset's todos (BC5 addition)." + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "name", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "ToggleGaugeRequestContent": { + "type": "object", + "properties": { + "gauge": { + "$ref": "#/components/schemas/GaugeTogglePayload" + } + }, + "required": [ + "gauge" + ] + }, + "Tool": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "name": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + } + }, + "required": [ + "created_at", + "enabled", + "id", + "name", + "title", + "updated_at" + ] + }, + "UnauthorizedErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "UpcomingAssignable": { + "type": "object", + "description": "A dated to-do, card or step as the upcoming-schedule report renders it.\n\nRendered by `app/views/api/schedules/calendar/_assignable.json.jbuilder`,\nwhich \u2014 like the calendar entry partial \u2014 writes its own keys instead of\nrendering `recordings/_recording`. Nothing else in the API returns this\nshape.\n\nThe member that bites: BC3 emits the item's text under `content`, not\n`title`. The previous model of this report declared `title`, so the field\ncallers most wanted was permanently absent while a key that was always\npresent went unmodelled.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "url": { + "type": "string", + "description": "The item's own Basecamp API URL." + }, + "app_url": { + "type": "string" + }, + "starts_on": { + "type": "string", + "description": "The item's start date, `null` unless this is a to-do that has one:\nKanban cards and steps both define `starts_on` as a literal nil to\nduck-type Todo, and the partial reads it unconditionally.\n\nThe key is in fact always present. It is modeled optional-and-nullable\nanyway, matching the deliberate treatment of `Todo.starts_on` and\n`Todo`/`Card.due_on` (see the fifth-g pass in\n`scripts/enhance-openapi-go-types.sh`): the static SDKs then type it\n`string | null | undefined`, which accepts the null this really sends and\nalso tolerates a partial payload. Nullability rides that same pass, which\nis what keeps the Go type `types.Date` rather than a bare string.", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "nullable": true + }, + "due_on": { + "type": "string", + "description": "The item's due date, `null` when it has none. Optional-and-nullable for\nthe same reason as starts_on.", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "nullable": true + }, + "type": { + "type": "string", + "description": "The item's kind, LOWERCASE and singular: \"todo\", \"card\" or \"step\".\n`short_recordable_name` demodulizes and downcases the recordable class,\nso this does not match the CamelCase `type` other recording projections\ncarry." + }, + "content": { + "type": "string", + "description": "The item's text \u2014 `recordable.title`. Spelled `content`, not `title`:\nthis partial names it after the Todo/Card content attribute rather than\nthe recording's title." + }, + "assignees": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UpcomingSchedulePerson" + }, + "description": "Present and possibly empty, never absent." + }, + "bucket": { + "$ref": "#/components/schemas/UpcomingScheduleBucket" + }, + "parent": { + "$ref": "#/components/schemas/UpcomingAssignableParent" + }, + "completion_url": { + "type": "string", + "description": "Where to POST/PUT the item's completion.\n\nOnly the to-do branch is an absolute URL: BC3 renders\n`completion_bucket_todo_url` for a to-do and\n`bucket_step_completions_path` for everything else, and a `_path` helper\nemits no host. Resolve this against the account base rather than assuming\nit is absolute." + }, + "completed": { + "type": "boolean" + }, + "repeating": { + "type": "boolean", + "description": "Whether the item repeats." + }, + "completion": { + "$ref": "#/components/schemas/UpcomingAssignableCompletion" + }, + "comments_count": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "app_url", + "assignees", + "bucket", + "comments_count", + "completed", + "completion_url", + "content", + "id", + "parent", + "repeating", + "status", + "type", + "url", + "visible_to_clients" + ] + }, + "UpcomingAssignableCompletion": { + "type": "object", + "description": "When an assignable was completed and by whom.", + "properties": { + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "creator": { + "$ref": "#/components/schemas/UpcomingSchedulePerson" + } + }, + "required": [ + "created_at", + "creator" + ] + }, + "UpcomingAssignableParent": { + "type": "object", + "description": "The to-do list, card column or parent to-do an assignable is filed under:\nid and title only, with no `type`, `url` or `app_url`.\n\nThe title is not always the parent's own: for a to-do inside a grouped\nlist, `todolist_or_group_title` folds in the grandparent list's title as\n\"List: Group\".", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "title" + ] + }, + "UpcomingScheduleBucket": { + "type": "object", + "description": "The project an upcoming-schedule item belongs to: id and name only.\n\nBoth calendar partials write `json.(recording.bucket, :id, :name)`, so this\nis TodoBucket minus `type`. That missing `type` is the first key a strict\ndecoder reaches on a populated response, ahead of any of the top-level\nmembers the calendar entry partial drops.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "UpcomingScheduleEntry": { + "type": "object", + "description": "One schedule entry as the upcoming-schedule report renders it.\n\nThis is NOT the ScheduleEntry projection. BC3 renders this report through\n`app/views/api/schedules/calendar/_entry.json.jbuilder`, a purpose-built\ncalendar partial, while GetScheduleEntry, GetScheduleEntryOccurrence,\nListScheduleEntries, CreateScheduleEntry and ReplaceScheduleEntry all render\n`schedules/entries/_entry.json.jbuilder` on top of the shared recording\npartial. The calendar partial does not render `recordings/_recording` at\nall \u2014 it writes its own keys \u2014 so relative to ScheduleEntry it drops\n`created_at`, `updated_at`, `title`, `inherits_status`, `parent`,\n`description_attachments`, `description`, `bookmark_url`,\n`subscription_url`, `comments_url`, `join_url`, `highlighted`,\n`boosts_count` and `boosts_url`, narrows `bucket` to id + name, and adds\n`recurring`, which no other schedule-entry projection emits.\n\nEvery member here is @required because the partial emits every key\nunconditionally: there is no `if` in it.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "url": { + "type": "string", + "description": "The entry's own Basecamp API URL. Unlike the ScheduleEntry projection,\nthis report never carries the entry's join link, so `url` here has no\n`join_url` sibling to be confused with." + }, + "app_url": { + "type": "string" + }, + "type": { + "type": "string", + "description": "Always the literal string \"ScheduleEntry\" \u2014 the calendar partial hardcodes\nit rather than deriving it from the recordable, so an occurrence reads\nback as \"ScheduleEntry\" too, not \"Schedule::Entry::Occurrence\"." + }, + "summary": { + "type": "string" + }, + "all_day": { + "type": "boolean", + "description": "Whether the entry occupies whole days rather than a time range.\nDiscriminates the two renderings of starts_at/ends_at below." + }, + "recurring": { + "type": "boolean", + "description": "Whether the entry repeats. Emitted only by this partial \u2014\n`recording.schedule_entry.recurring?`, i.e. whether the entry carries a\nrecurrence schedule.\n\nIt also discriminates the two envelope arrays: BC3 selects\n`schedule_entries` with `recurrence_schedule: nil` and\n`recurring_schedule_entry_occurrences` with `recurrence_schedule` NOT\nNULL, so this reads false throughout the first array and true throughout\nthe second." + }, + "starts_at": { + "type": "string", + "description": "A date for an all-day entry and a full timestamp for a timed one \u2014\n`starts_at_date_or_time`, the same rendering ScheduleEntry documents.\nTreat it as opaque and round-trip it verbatim; the all_day sibling\ndiscriminates.", + "x-go-type": "types.FlexibleTime", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "ends_at": { + "type": "string", + "description": "See starts_at for the date-vs-timestamp rendering.", + "x-go-type": "types.FlexibleTime", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "creator": { + "$ref": "#/components/schemas/UpcomingSchedulePerson" + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UpcomingSchedulePerson" + }, + "description": "Everyone attending. Present and possibly empty, never absent." + }, + "bucket": { + "$ref": "#/components/schemas/UpcomingScheduleBucket" + }, + "comments_count": { + "type": "integer", + "description": "Comments on the entry \u2014 or, for an occurrence, on that occurrence.", + "format": "int32" + } + }, + "required": [ + "all_day", + "app_url", + "bucket", + "comments_count", + "creator", + "ends_at", + "id", + "participants", + "recurring", + "starts_at", + "status", + "summary", + "type", + "url", + "visible_to_clients" + ] + }, + "UpcomingSchedulePerson": { + "type": "object", + "description": "A person as the calendar partials render them: id, name and avatar only.\n\n`schedules/calendar/_person.json.jbuilder` delegates to\n`people/_person_minimal.json.jbuilder`, the same three-key partial that\nMyAssignmentAssignee and OutOfOfficePerson already model separately from the\nfull Person projection. Reusing Person here would decode \u2014 its only\n@required members are id and name \u2014 but would advertise two dozen members\nthis report never sends.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string", + "format": "password" + }, + "avatar_url": { + "type": "string", + "format": "password" + } + }, + "required": [ + "avatar_url", + "id", + "name" + ] + }, + "UpdateAccountLogoInputPayload": { + "type": "string", + "contentEncoding": "byte" + }, + "UpdateAccountNameRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "UpdateAccountNameResponseContent": { + "$ref": "#/components/schemas/Account" + }, + "UpdateCalendarRequestContent": { + "type": "object", + "properties": { + "calendar": { + "$ref": "#/components/schemas/CalendarAttributes" + } + }, + "required": [ + "calendar" + ] + }, + "UpdateCalendarResponseContent": { + "$ref": "#/components/schemas/Calendar" + }, + "UpdateCampfireLineRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "The new line content, interpreted as rich text (HTML)" + } + }, + "required": [ + "content" + ] + }, + "UpdateCardColumnRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "UpdateCardColumnResponseContent": { + "$ref": "#/components/schemas/CardColumn" + }, + "UpdateCardRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "assignee_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + }, + "UpdateCardResponseContent": { + "$ref": "#/components/schemas/Card" + }, + "UpdateCardStepRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "due_on": { + "type": "string", + "x-go-type": "types.Date", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "assignee_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + }, + "UpdateCardStepResponseContent": { + "$ref": "#/components/schemas/CardStep" + }, + "UpdateChatbotRequestContent": { + "type": "object", + "properties": { + "service_name": { + "type": "string" + }, + "command_url": { + "type": "string" + } + }, + "required": [ + "service_name" + ] + }, + "UpdateChatbotResponseContent": { + "$ref": "#/components/schemas/Chatbot" + }, + "UpdateCloudFileRequestContent": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "service": { + "type": "string", + "description": "Short identifier for the external service \u2014 \"dropbox\", \"google_doc\",\n\"figma\", \"other\", \u2026 Derived from the CloudFile::Service subclass name, so it\nis always present. `other` accepts any well-formed HTTPS URL." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "required": [ + "service", + "url" + ] + }, + "UpdateCloudFileResponseContent": { + "$ref": "#/components/schemas/CloudFile" + }, + "UpdateCommentRequestContent": { + "type": "object", + "properties": { + "content": { + "type": "string" + } + }, + "required": [ + "content" + ] + }, + "UpdateCommentResponseContent": { + "$ref": "#/components/schemas/Comment" + }, + "UpdateFolderRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The folder's new name. Blank is rejected with 422 \u2014 unlike create, update\ndoes not fall back to a default name." + } + }, + "required": [ + "name" + ] + }, + "UpdateFolderResponseContent": { + "$ref": "#/components/schemas/FolderWithProjects" + }, + "UpdateGaugeNeedleRequestContent": { + "type": "object", + "properties": { + "gauge_needle": { + "$ref": "#/components/schemas/GaugeNeedleUpdatePayload" + } + } + }, + "UpdateGaugeNeedleResponseContent": { + "$ref": "#/components/schemas/GaugeNeedle" + }, + "UpdateGoogleDocumentRequestContent": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "document_type": { + "type": "string", + "description": "One of \"doc\", \"sheet\", \"slide\", \"other\". Backed by a Rails enum, so an\nunrecognized value is rejected up front with a field-keyed 422\n({\"errors\": {\"document_type\": [\"is not a valid document type\"]}}) rather\nthan reaching validation." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "status": { + "type": "string", + "description": "active|drafted" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "required": [ + "document_type", + "url" + ] + }, + "UpdateGoogleDocumentResponseContent": { + "$ref": "#/components/schemas/GoogleDocument" + }, + "UpdateHillChartSettingsRequestContent": { + "type": "object", + "properties": { + "tracked": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "untracked": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + }, + "UpdateHillChartSettingsResponseContent": { + "$ref": "#/components/schemas/HillChart" + }, + "UpdateLineupMarkerRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "date": { + "type": "string" + } + } + }, + "UpdateMessageRequestContent": { + "type": "object", + "properties": { + "subject": { + "type": "string" + }, + "content": { + "type": "string" + }, + "status": { + "type": "string", + "description": "active|drafted" + }, + "category_id": { + "type": "integer", + "format": "int64" + } + } + }, + "UpdateMessageResponseContent": { + "$ref": "#/components/schemas/Message" + }, + "UpdateMessageTypeRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "icon": { + "type": "string" + } + } + }, + "UpdateMessageTypeResponseContent": { + "$ref": "#/components/schemas/MessageType" + }, + "UpdateMyNoteRequestContent": { + "type": "object", + "properties": { + "note": { + "$ref": "#/components/schemas/MyNoteAttributes" + } + }, + "required": [ + "note" + ] + }, + "UpdateMyNoteResponseContent": { + "$ref": "#/components/schemas/MyNote" + }, + "UpdateMyPreferencesRequestContent": { + "type": "object", + "properties": { + "person": { + "$ref": "#/components/schemas/PreferencesPayload" + } + }, + "required": [ + "person" + ] + }, + "UpdateMyPreferencesResponseContent": { + "$ref": "#/components/schemas/Preferences" + }, + "UpdateMyProfileRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "format": "password" + }, + "email_address": { + "type": "string", + "format": "password" + }, + "title": { + "type": "string", + "format": "password" + }, + "bio": { + "type": "string", + "format": "password" + }, + "location": { + "type": "string", + "format": "password" + }, + "time_zone_name": { + "type": "string" + }, + "first_week_day": { + "$ref": "#/components/schemas/FirstWeekDay" + }, + "time_format": { + "type": "string" + } + } + }, + "UpdateProjectAccessRequestContent": { + "type": "object", + "properties": { + "grant": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "revoke": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "create": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CreatePersonRequest" + } + } + } + }, + "UpdateProjectAccessResponseContent": { + "$ref": "#/components/schemas/ProjectAccessResult" + }, + "UpdateProjectRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "admissions": { + "type": "string", + "description": "invite|employee|team" + }, + "schedule_attributes": { + "$ref": "#/components/schemas/ScheduleAttributes" + } + }, + "required": [ + "name" + ] + }, + "UpdateProjectResponseContent": { + "$ref": "#/components/schemas/Project" + }, + "UpdateQuestionNotificationSettingsRequestContent": { + "type": "object", + "properties": { + "notify_on_answer": { + "type": "boolean", + "description": "Notify when someone answers" + }, + "digest_include_unanswered": { + "type": "boolean", + "description": "Include unanswered in digest" + } + } + }, + "UpdateQuestionNotificationSettingsResponseContent": { + "type": "object", + "properties": { + "responding": { + "type": "boolean" + }, + "subscribed": { + "type": "boolean" + } + } + }, + "UpdateQuestionRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "schedule": { + "$ref": "#/components/schemas/QuestionSchedule" + }, + "paused": { + "type": "boolean" + } + } + }, + "UpdateQuestionResponseContent": { + "$ref": "#/components/schemas/Question" + }, + "UpdateScheduleSettingsRequestContent": { + "type": "object", + "properties": { + "include_due_assignments": { + "type": "boolean" + } + }, + "required": [ + "include_due_assignments" + ] + }, + "UpdateScheduleSettingsResponseContent": { + "$ref": "#/components/schemas/Schedule" + }, + "UpdateSubscriptionRequestContent": { + "type": "object", + "properties": { + "subscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "unsubscriptions": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + } + }, + "UpdateSubscriptionResponseContent": { + "$ref": "#/components/schemas/Subscription" + }, + "UpdateTemplateRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "UpdateTemplateResponseContent": { + "$ref": "#/components/schemas/Template" + }, + "UpdateTimesheetEntryRequestContent": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "hours": { + "type": "string" + }, + "description": { + "type": "string" + }, + "person_id": { + "type": "integer", + "format": "int64" + } + } + }, + "UpdateTimesheetEntryResponseContent": { + "$ref": "#/components/schemas/TimesheetEntry" + }, + "UpdateTodolistOrGroupRequestContent": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name (required for a to-do list and for a group alike) - presence-validated server-side, so omitting it is a 422, not a preserve" + }, + "description": { + "type": "string", + "description": "Description (rich text HTML) - writable for a todolist group as well as a todolist, and omitting it clears it either way" + } + }, + "required": [ + "name" + ] + }, + "UpdateTodolistOrGroupResponseContent": { + "$ref": "#/components/schemas/Todolist" + }, + "UpdateToolRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + }, + "required": [ + "title" + ] + }, + "UpdateToolResponseContent": { + "$ref": "#/components/schemas/Tool" + }, + "UpdateUploadRequestContent": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "base_name": { + "type": "string" + } + } + }, + "UpdateUploadResponseContent": { + "$ref": "#/components/schemas/Upload" + }, + "UpdateVaultRequestContent": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + } + }, + "UpdateVaultResponseContent": { + "$ref": "#/components/schemas/Vault" + }, + "UpdateWebhookRequestContent": { + "type": "object", + "properties": { + "payload_url": { + "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "boolean" + } + } + }, + "UpdateWebhookResponseContent": { + "$ref": "#/components/schemas/Webhook" + }, + "UpdateWormholeRequestContent": { + "type": "object", + "properties": { + "destination_recording_id": { + "type": "integer", + "description": "Id of the new destination column (on another accessible card table).", + "format": "int64" + } + }, + "required": [ + "destination_recording_id" + ] + }, + "UpdateWormholeResponseContent": { + "$ref": "#/components/schemas/Wormhole" + }, + "Upload": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "description": { + "type": "string" + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + }, + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "width": { + "type": "integer", + "format": "int32", + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "height": { + "type": "integer", + "format": "int32", + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + } + }, + "download_url": { + "type": "string", + "x-basecamp-auth-routable-url": {} + }, + "filename": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "description_attachments", + "id", + "inherits_status", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "UploadVersion": { + "type": "object", + "description": "A version event for an upload, from GET /uploads/{id}/versions.json.\n\n`action` is one of `created`, `active` (the upload's publication) or `blob_changed`\n(a file replacement). To list the file's PAST versions, select entries that carry\nan `upload` whose `current` is false \u2014 the original file arrives as `created` or\n`active`, never `blob_changed`, so filtering on that action drops the original\nand keeps the current file instead. This is an Event plus the file it recorded, rendered by its own\npartial rather than the shared event one, so upload fields don't leak onto todo,\nmessage and card events.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "recording_id": { + "type": "integer", + "format": "int64" + }, + "action": { + "type": "string" + }, + "details": { + "$ref": "#/components/schemas/EventDetails" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "upload": { + "$ref": "#/components/schemas/UploadVersionFile" + } + }, + "required": [ + "action", + "created_at", + "creator", + "id", + "recording_id" + ] + }, + "UploadVersionFile": { + "type": "object", + "description": "The file a version event recorded \u2014 a reduced projection, not an Upload.", + "properties": { + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "filename": { + "type": "string" + }, + "download_url": { + "type": "string", + "description": "Fetches THIS version's bytes. The upload's own download_url always serves the\nlatest, which is the whole point of the feature.", + "x-basecamp-auth-routable-url": {} + }, + "app_download_url": { + "type": "string" + }, + "current": { + "type": "boolean", + "description": "True for the newest version *event*, and for exactly one element of any\nnon-empty response. The renderer computes it positionally \u2014 `event ==\n@events.first` over a reverse-chronological list \u2014 so it is a property of\nordering, not of what the upload currently points at, and it cannot be\nzero or plural.\n\nThat distinction is the whole caveat: `current` does NOT mean \"this is the\nfile the upload's own download_url serves\". A metadata-only PUT swaps in a\nrecordable carrying the same blob and emits no event, so afterwards no\nevent references the upload's current recordable \u2014 and this flag still\nmarks exactly one element, the newest event. bc3 pins that case by name in\n\"exactly one version is current after a metadata-only update\"." + } + }, + "required": [ + "app_download_url", + "current", + "download_url", + "filename" + ] + }, + "ValidationErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "Vault": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "documents_count": { + "type": "integer", + "format": "int32" + }, + "documents_url": { + "type": "string" + }, + "uploads_count": { + "type": "integer", + "format": "int32" + }, + "uploads_url": { + "type": "string" + }, + "vaults_count": { + "type": "integer", + "format": "int32" + }, + "vaults_url": { + "type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "created_at", + "creator", + "id", + "inherits_status", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + }, + "Webhook": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "active": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "payload_url": { + "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-type-skip-optional-pointer": true + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "recent_deliveries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDelivery" + }, + "description": "Up to the 25 most recent delivery exchanges, most recent first.\nEmpty when the webhook hasn't delivered anything yet.", + "x-go-type-skip-optional-pointer": true + } + }, + "required": [ + "app_url", + "created_at", + "id", + "payload_url", + "updated_at", + "url" + ] + }, + "WebhookCopy": { + "type": "object", + "description": "Reference to a copied/moved recording in copy events.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bucket": { + "$ref": "#/components/schemas/WebhookCopyBucket" + } + } + }, + "WebhookCopyBucket": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + } + } + }, + "WebhookDelivery": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "request": { + "$ref": "#/components/schemas/WebhookDeliveryRequest" + }, + "response": { + "$ref": "#/components/schemas/WebhookDeliveryResponse" + } + } + }, + "WebhookDeliveryRequest": { + "type": "object", + "properties": { + "headers": { + "$ref": "#/components/schemas/WebhookHeadersMap" + }, + "body": { + "$ref": "#/components/schemas/WebhookEvent" + } + } + }, + "WebhookDeliveryResponse": { + "type": "object", + "properties": { + "headers": { + "$ref": "#/components/schemas/WebhookHeadersMap" + }, + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + }, + "WebhookEvent": { + "type": "object", + "description": "The event payload delivered to webhook URLs.\nThis is the body of an outbound webhook HTTP request.\nAlso appears as the body field in WebhookDelivery.request.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "kind": { + "type": "string" + }, + "details": {}, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "recording": { + "$ref": "#/components/schemas/Recording" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "copy": { + "$ref": "#/components/schemas/WebhookCopy" + } + } + }, + "WebhookHeadersMap": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "WebhookLimitErrorResponseContent": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "Wormhole": { + "type": "object", + "description": "A wormhole links this card table to a column on another card table, enabling\ncards to move across projects. It carries the full recording representation\nplus the destination-linkage fields. The wormhole's own `url`/`app_url`/`parent`\npoint at the *source* board; `destination_url` is the only field identifying\nthe destination column.", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + } + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/TodoBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "color": { + "type": [ + "string", + "null" + ], + "description": "Wormhole color; always emitted on the wire (`json.color recording.color`),\n`null` when unset. Like destination_url, `@required` models the presence and\nthe nullability is layered on in the OpenAPI (smithy-build.json jsonAdd ->\ntype: [\"string\",\"null\"]). Go types it *string because the value is nullable,\nnot because it is optional \u2014 the field is @required and carries no omitempty.", + "x-go-type": "string" + }, + "linked": { + "type": "boolean", + "description": "True only while the destination column, its board, and its bucket are all\nactive; false once the destination is unlinked. Always emitted." + }, + "destination_url": { + "type": [ + "string", + "null" + ], + "description": "URL of the destination column; always present on the wire, `null` for an\nunlinked wormhole. `@required` models the presence; the nullability of the\nvalue is layered on in the OpenAPI (smithy-build.json jsonAdd -> type:\n[\"string\",\"null\"]) since Smithy has no native\nrequired-and-nullable \u2014 exactly the SearchType.key treatment. SDKs model it\nas required-but-nullable (`string | null`, not `string | null | undefined`).", + "x-go-type": "string" + } + }, + "required": [ + "app_url", + "bucket", + "color", + "created_at", + "creator", + "destination_url", + "id", + "inherits_status", + "linked", + "parent", + "status", + "title", + "type", + "updated_at", + "url", + "visible_to_clients" + ] + } + } + } +} diff --git a/internal/mcpserver/server.go b/internal/mcpserver/server.go new file mode 100644 index 00000000..1b7b4ca9 --- /dev/null +++ b/internal/mcpserver/server.go @@ -0,0 +1,68 @@ +package mcpserver + +import ( + "fmt" + "log/slog" + + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/basecamp/mcp/gateway" + + "github.com/basecamp/basecamp-cli/internal/version" +) + +// Name identifies the server in the MCP initialize handshake. The version is +// the CLI's own: `basecamp mcp` is the CLI serving MCP, not a separate +// product. +const Name = "basecamp-cli" + +// Config selects the served tool surface. +type Config struct { + // ReadOnly drops every write action from the catalog and refuses write + // dispatch outright. + ReadOnly bool + // Domains narrows the served domains by key ("projects", "todos", ...). + // Empty means all. Unknown keys are a startup error — fail closed. + Domains []string +} + +// Server wraps the toolkit gateway serving Basecamp's derived catalog, +// dispatching through the CLI's authenticated, account-scoped SDK client. +type Server struct { + gw *gateway.Server +} + +// New derives the catalog and hands it to the gateway, which applies the +// config's domain and read-only filters. Tool calls dispatch through api. +func New(api API, cfg Config) (*Server, error) { + if api == nil { + return nil, fmt.Errorf("mcpserver: API client is required") + } + + cat, err := loadCatalog() + if err != nil { + return nil, fmt.Errorf("derive catalog: %w", err) + } + + gw, err := gateway.New(cat.GatewayDomains(), gateway.Config{ + ReadOnly: cfg.ReadOnly, + Domains: cfg.Domains, + Handler: dispatcher{api: api}.handle, + }) + if err != nil { + return nil, err + } + + return &Server{gw: gw}, nil +} + +// Domains returns the served domains. +func (s *Server) Domains() []gateway.Domain { + return s.gw.Domains() +} + +// BuildMCPServer constructs the SDK MCP server with one gateway tool per +// served domain. +func (s *Server) BuildMCPServer(logger *slog.Logger) *mcp.Server { + return s.gw.BuildMCPServer(&mcp.Implementation{Name: Name, Version: version.Version}, logger) +} diff --git a/internal/mcpserver/server_test.go b/internal/mcpserver/server_test.go new file mode 100644 index 00000000..4711a765 --- /dev/null +++ b/internal/mcpserver/server_test.go @@ -0,0 +1,242 @@ +package mcpserver + +import ( + "context" + "encoding/json" + "log/slog" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/basecamp/basecamp-sdk/go/pkg/basecamp" + + "github.com/basecamp/mcp/mcptest" +) + +type testTokenProvider struct{} + +func (testTokenProvider) AccessToken(context.Context) (string, error) { + return "test-token", nil +} + +// newTestAPI returns an account-scoped basecamp-sdk client — the same kind +// the CLI hands the server — pointed at upstream. +func newTestAPI(upstream *httptest.Server) *basecamp.AccountClient { + client := basecamp.NewClient(&basecamp.Config{BaseURL: upstream.URL}, testTokenProvider{}, + basecamp.WithMaxRetries(1)) + return client.ForAccount("999") +} + +func TestServerListsDomainTools(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + tools := mcptest.ListTools(t, session) + assert.Len(t, tools, 15) + require.Contains(t, tools, "basecamp_projects") + projects := tools["basecamp_projects"] + assert.Contains(t, projects.Description, "list_projects") + assert.Contains(t, projects.Description, "create_project") +} + +func TestServerDescribeServesSchemas(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + text, isError := mcptest.CallText(t, session, "basecamp_projects", map[string]any{ + "action": "describe", + "params": map[string]any{"action": "get_project"}, + }) + require.False(t, isError, "describe failed: %s", text) + assert.Contains(t, text, "projectId") + assert.NotContains(t, text, "accountId", "describe must not advertise the account parameter the CLI supplies") +} + +func TestServerDispatchesThroughSDKClient(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + require.Equal(t, "/999/projects.json", r.URL.Path) + require.Equal(t, "archived", r.URL.Query().Get("status")) + require.Equal(t, "Bearer test-token", r.Header.Get("Authorization")) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[{"id":1,"name":"Making a Podcast"}]`)) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + text, isError := mcptest.CallText(t, session, "basecamp_projects", map[string]any{ + "action": "list_projects", + "params": map[string]any{"status": "archived"}, + }) + require.False(t, isError, "list_projects failed: %s", text) + var projects []struct { + Name string `json:"name"` + } + require.NoError(t, json.Unmarshal([]byte(text), &projects)) + require.Len(t, projects, 1) + assert.Equal(t, "Making a Podcast", projects[0].Name) +} + +func TestServerWrapsBodyAndCreates(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/999/todolists/2/todos.json", r.URL.Path) + var body map[string]any + require.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + require.Equal(t, "Buy milk", body["content"]) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(`{"id":3,"content":"Buy milk"}`)) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + text, isError := mcptest.CallText(t, session, "basecamp_todos", map[string]any{ + "action": "create_todo", + "params": map[string]any{"todolistId": "2", "content": "Buy milk"}, + }) + require.False(t, isError, "create_todo failed: %s", text) + assert.Contains(t, text, `"id":3`) +} + +func TestServerSurfacesEmptyBodyStatus(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/999/todos/2/completion.json", r.URL.Path) + w.WriteHeader(http.StatusNoContent) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + text, isError := mcptest.CallText(t, session, "basecamp_todos", map[string]any{ + "action": "complete_todo", + "params": map[string]any{"todoId": "2"}, + }) + require.False(t, isError, "complete_todo failed: %s", text) + assert.Contains(t, text, `"status": 204`) +} + +func TestServerSurfacesPagination(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Link", `; rel="next"`) + _, _ = w.Write([]byte(`[{"id":1}]`)) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + text, isError := mcptest.CallText(t, session, "basecamp_projects", map[string]any{ + "action": "list_projects", + "params": map[string]any{}, + }) + require.False(t, isError, "list_projects failed: %s", text) + var wrapped struct { + NextPage string `json:"next_page"` + Results json.RawMessage `json:"results"` + } + require.NoError(t, json.Unmarshal([]byte(text), &wrapped)) + assert.Equal(t, "2", wrapped.NextPage) + assert.JSONEq(t, `[{"id":1}]`, string(wrapped.Results)) +} + +func TestServerSurfacesAPIErrorsInBand(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, `{"error":"Record not found"}`, http.StatusNotFound) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + text, isError := mcptest.CallText(t, session, "basecamp_projects", map[string]any{ + "action": "get_project", + "params": map[string]any{"projectId": "12345"}, + }) + assert.True(t, isError, "a 404 must come back in-band, got: %s", text) + assert.NotEmpty(t, text) +} + +func TestServerReadOnlyDropsWrites(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{ReadOnly: true}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + tools := mcptest.ListTools(t, session) + require.Contains(t, tools, "basecamp_projects") + projects := tools["basecamp_projects"] + assert.Contains(t, projects.Description, "list_projects") + assert.NotContains(t, projects.Description, "create_project") + require.NotNil(t, projects.Annotations) + assert.True(t, projects.Annotations.ReadOnlyHint) + + text, isError := mcptest.CallText(t, session, "basecamp_projects", map[string]any{ + "action": "create_project", + "params": map[string]any{"name": "Nope"}, + }) + assert.True(t, isError, "write dispatch must be refused in read-only mode, got: %s", text) +} + +func TestServerDomainNarrowing(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + })) + t.Cleanup(upstream.Close) + + srv, err := New(newTestAPI(upstream), Config{Domains: []string{"projects", "todos"}}) + require.NoError(t, err) + session := mcptest.Connect(t, srv.BuildMCPServer(slog.New(slog.DiscardHandler))) + + tools := mcptest.ListTools(t, session) + assert.Len(t, tools, 2) + assert.Contains(t, tools, "basecamp_projects") + assert.Contains(t, tools, "basecamp_todos") +} + +func TestServerUnknownDomainFailsClosed(t *testing.T) { + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("unexpected HTTP request: %s %s", r.Method, r.URL.Path) + })) + t.Cleanup(upstream.Close) + + _, err := New(newTestAPI(upstream), Config{Domains: []string{"projects", "nonsense"}}) + require.Error(t, err) + assert.Contains(t, err.Error(), `unknown domain "nonsense"`) +} + +func TestServerRequiresAPI(t *testing.T) { + _, err := New(nil, Config{}) + assert.Error(t, err) +} diff --git a/internal/mcpserver/testdata/catalog_snapshot.txt b/internal/mcpserver/testdata/catalog_snapshot.txt new file mode 100644 index 00000000..a9a1024f --- /dev/null +++ b/internal/mcpserver/testdata/catalog_snapshot.txt @@ -0,0 +1,367 @@ +== basecamp_projects +Basecamp projects: list, get, create, update, archive, unarchive, and trash. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- archive_project: Archive a project, removing it from the active project list (returns 204 No Content) +- create_project: Create a new project +- get_project (RO): Get a single project by id +- list_projects (RO, paginated): List projects (active by default; optionally archived/trashed) +- trash_project: Trash a project (returns 204 No Content) +- unarchive_project: Restore a project to active status from trash as well as from the archive (returns 204 No Content) +- update_project: Update an existing project + +== basecamp_todos +Todos, todolists, todolist groups, and todosets — plus each todolist's hill chart. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- complete_todo: Mark a todo as complete +- create_todo: Create a new todo in a todolist +- create_todolist: Create a new todolist in a todoset +- create_todolist_group: Create a new group in a todolist +- create_todoset_todo: Create a to-do directly under a project's to-do set, outside any to-do list +- get_hill_chart (RO): Get the hill chart for a todoset +- get_todo (RO): Get a single todo by id +- get_todolist_or_group (RO): Get a single todolist or todolist group by id The endpoint is polymorphic, but it answers with one shape: BC3 has no group model, so both variants come back as a flat Todolist (see the Todolist shape) +- get_todoset (RO): Get a todoset (container for todolists in a project) +- list_todolist_groups (RO, paginated): List groups in a todolist +- list_todolists (RO, paginated): List todolists in a todoset +- list_todos (RO, paginated): List todos in a todolist +- replace_todo: Replace a todo with a new complete representation +- reposition_todo: Reposition a todo within its todolist +- reposition_todolist: Reposition a to-do list within its to-do set +- reposition_todolist_group: Reposition a todolist group +- uncomplete_todo: Mark a todo as incomplete +- update_hill_chart_settings: Track or untrack todolists on a hill chart +- update_todolist_or_group: Replace a todolist (or todolist group) with a new complete representation + +== basecamp_cards +Card tables (kanban): cards, columns, steps, and wormholes, with moves, repositioning, and on-hold state. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_card: Create a card in a column +- create_card_column: Create a column in a card table +- create_card_step: Create a step on a card +- create_wormhole: Create a wormhole linking this card table to a column on another card table +- delete_wormhole: Delete a wormhole +- disable_card_column_on_hold: Disable on-hold section in a column +- enable_card_column_on_hold: Enable on-hold section in a column +- get_card (RO): Get a card by ID +- get_card_column (RO): Get a card column by ID +- get_card_step (RO): Get a step by ID +- get_card_table (RO): Get a card table by ID +- list_cards (RO, paginated): List cards in a column +- move_card: Move a card to a different column +- move_card_column: Move a column within a card table +- reposition_card_step: Reposition a step within a card +- set_card_column_color: Set the color of a column +- set_card_step_completion: Set card step completion status (PUT with completion: "on" to complete, "" to uncomplete) +- subscribe_to_card_column: Subscribe to a card column (watch for changes) +- unsubscribe_from_card_column: Unsubscribe from a card column (stop watching for changes) +- update_card: Update an existing card +- update_card_column: Update an existing column +- update_card_step: Update an existing step +- update_wormhole: Update a wormhole's destination column + +== basecamp_messages +Message boards: messages, comments, message types (categories), and pinning. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_comment: Create a new comment on a recording +- create_message: Create a new message on a message board +- create_message_type: Create a new message type in a project +- delete_message_type: Delete a message type +- get_comment (RO): Get a single comment by id +- get_message (RO): Get a single message by id +- get_message_board (RO): Get a message board +- get_message_type (RO): Get a single message type by id +- list_comments (RO, paginated): List comments on a recording +- list_message_types (RO, paginated): List message types in a project +- list_messages (RO, paginated): List messages on a message board +- pin_message: Pin a message to the top of the message board +- unpin_message: Unpin a message from the message board +- update_comment: Update an existing comment +- update_message: Update an existing message +- update_message_type: Update an existing message type + +== basecamp_campfires +Campfire chat: rooms, chat lines, uploads, and chatbots. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_campfire_line: Create a new line (message) in a campfire +- create_chatbot: Create a new chatbot for a campfire +- delete_campfire_line: Delete a campfire line; allowed for the line's creator or an admin +- delete_chatbot: Delete a chatbot +- get_campfire (RO): Get a campfire by ID +- get_campfire_line (RO): Get a campfire line by ID +- get_chatbot (RO): Get a chatbot by ID +- list_campfire_lines (RO, paginated): List all lines (messages) in a campfire +- list_campfire_uploads (RO, paginated): List uploaded files in a campfire +- list_campfires (RO, paginated): List all campfires across the account +- list_chatbots (RO, paginated): List all chatbots for a campfire +- update_campfire_line: Update an existing campfire line; the content is always treated as rich text (HTML) +- update_chatbot: Update an existing chatbot + +== basecamp_boosts +Boosts (emoji reactions) on recordings and their events: list, get, create, and delete. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_event_boost: Create a boost on a specific event within a recording +- create_recording_boost: Create a boost on a recording +- delete_boost: Delete a boost +- get_boost (RO): Get a single boost +- list_event_boosts (RO, paginated): List boosts on a specific event within a recording +- list_recording_boosts (RO, paginated): List boosts on a recording + +== basecamp_schedules +Schedules and schedule entries (calendar events), timesheets and time tracking, and per-account calendars. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_schedule_entry: Create a new schedule entry +- create_timesheet_entry: Create a timesheet entry on a recording +- destroy_timesheet_entry: Permanently delete a timesheet entry; answers 403 when the caller may not archive or trash it +- get_calendar (RO): Get a calendar by its bucket id +- get_project_timesheet (RO, paginated): Get timesheet for a specific project +- get_recording_timesheet (RO, paginated): Get timesheet for a specific recording +- get_schedule (RO): Get a schedule +- get_schedule_entry (RO): Get a single schedule entry by id +- get_schedule_entry_occurrence (RO): Get a specific occurrence of a recurring schedule entry +- get_timesheet_entry (RO): Get a single timesheet entry +- get_timesheet_report (RO): Get account-wide timesheet report +- list_schedule_entries (RO, paginated): List entries on a schedule +- replace_schedule_entry: Replace a schedule entry with a new complete representation +- update_calendar: Update a calendar's display color +- update_schedule_settings: Update schedule settings +- update_timesheet_entry: Update a timesheet entry + +== basecamp_files +Docs & Files: vaults (folders), documents, uploads, attachments, cloud file links, Google documents, and home-screen project folders. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_cloud_file: Create a new cloud file in a vault +- create_document: Create a new document in a vault +- create_folder: Create a folder for the authenticated user and file the given projects into it +- create_google_document: Create a new Google document in a vault +- create_upload: Create a new upload in a vault +- create_upload_version: Replace an upload's file with a new version +- create_vault: Create a new vault (subfolder) in a vault +- delete_folder: Delete a folder and unpin its projects from the home screen (returns 204 No Content) +- get_cloud_file (RO): Get a single cloud file by id +- get_document (RO): Get a single document by id +- get_folder (RO): Get one folder, with the projects grouped inside it expanded under `projects` +- get_google_document (RO): Get a single Google document by id +- get_upload (RO): Get a single upload by id +- get_vault (RO): Get a single vault by id +- list_documents (RO, paginated): List documents in a vault +- list_folders (RO): List the authenticated user's folders in home-screen order +- list_upload_versions (RO, paginated): List versions of an upload +- list_uploads (RO, paginated): List uploads in a vault +- list_vaults (RO, paginated): List vaults (subfolders) in a vault +- replace_document: Replace a document with a new complete representation +- update_cloud_file: Replace a cloud file with a new complete representation +- update_folder: Rename a folder +- update_google_document: Replace a Google document with a new complete representation +- update_upload: Update an existing upload +- update_vault: Update an existing vault + +== basecamp_people +People and access: profiles, pingable people, project access, out-of-office, preferences, and notification subscriptions. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- disable_out_of_office: Disable out of office for a person +- enable_out_of_office: Enable or replace out of office for a person +- get_my_preferences (RO): Get the current user's preferences +- get_my_profile (RO): Get the current authenticated user's profile +- get_out_of_office (RO): Get the out of office status for a person +- get_person (RO): Get a person by ID +- get_subscription (RO): Get subscription information for a recording +- list_people (RO, paginated): List all people visible to the current user +- list_pingable_people (RO, paginated): List all account users who can be pinged +- list_project_people (RO, paginated): List all active people on a project +- subscribe: Subscribe the current user to a recording +- unsubscribe: Unsubscribe the current user from a recording +- update_my_preferences: Update the current user's preferences +- update_my_profile: Update the current authenticated user's profile (returns 204 No Content) +- update_project_access: Update project access (grant/revoke/create people) +- update_subscription: Update subscriptions by adding or removing specific users + +== basecamp_automation +Automatic check-ins (questionnaires, questions, answers, reminders), project templates, webhooks, lineup markers, dock tools, recording lifecycle (archive/trash), change events, and search. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- archive_recording: Archive a recording +- create_answer: Create a new answer for a question +- create_lineup_marker: Create a new lineup marker +- create_project_from_template: Create a project from a template (asynchronous) +- create_question: Create a new question in a questionnaire +- create_template: Create a new template +- create_tool: Create a tool in a project dock +- create_webhook: Create a new webhook for a project +- delete_lineup_marker: Delete a lineup marker +- delete_template: Delete a template (trash it) +- delete_tool: Delete a tool (trash it) +- delete_webhook: Delete a webhook +- disable_tool: Disable a tool (hide it from the project dock) +- enable_tool: Enable a tool (show it on the project dock) +- get_answer (RO): Get a single answer by id +- get_answers_by_person (RO, paginated): Get all answers from a specific person for a question +- get_project_construction (RO): Get the status of a project construction +- get_question (RO): Get a single question by id +- get_question_reminders (RO, paginated): Get pending check-in reminders for the current user +- get_questionnaire (RO): Get a questionnaire (automatic check-ins container) by id +- get_search_metadata (RO): Get search metadata (available filter options) +- get_template (RO): Get a single template by id +- get_tool (RO): Get a dock tool by id +- get_webhook (RO): Get a single webhook by id +- list_answers (RO, paginated): List all answers for a question +- list_events (RO, paginated): List all events for a recording +- list_lineup_markers (RO): List all lineup markers for the account +- list_question_answerers (RO, paginated): List all people who have answered a question (answerers) +- list_questions (RO, paginated): List all questions in a questionnaire +- list_recordings (RO, paginated): List recordings of a given type across projects +- list_templates (RO, paginated): List all templates visible to the current user +- list_webhooks (RO, paginated): List all webhooks for a project +- pause_question: Pause a check-in question (stops sending reminders) +- reposition_tool: Reposition a tool on the project dock +- resume_question: Resume a paused check-in question (resumes sending reminders) +- search (RO, paginated): Search for content across the account +- trash_recording: Trash a recording +- unarchive_recording: Unarchive a recording (restore to active status) +- update_answer: Update an existing answer +- update_lineup_marker: Update an existing lineup marker +- update_question: Update an existing question +- update_question_notification_settings: Update notification settings for a check-in question +- update_template: Update an existing template +- update_tool: Update (rename) an existing tool +- update_webhook: Update an existing webhook + +== basecamp_reports +Reports and timelines: progress, assigned and overdue todos, upcoming schedule, per-person progress, and project timelines. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- get_assigned_todos (RO): Get todos assigned to a specific person +- get_overdue_todos (RO): Get overdue todos grouped by lateness +- get_person_progress (RO, paginated): Get a person's activity timeline +- get_progress_report (RO, paginated): Get account-wide activity feed (progress report) +- get_project_timeline (RO, paginated): Get project timeline +- get_upcoming_schedule (RO): Get upcoming schedule entries and assignable items within a date window +- list_assignable_people (RO): List people who can be assigned todos + +== basecamp_everything +Account-wide feeds: every checkin, comment, file, forward, and message, and cards and todos filtered by state (open, completed, overdue, unassigned, no due date, not now). + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- get_everything_checkins (RO, paginated): Get every automatic check-in answer across all accessible projects, newest-first +- get_everything_comments (RO, paginated): Get every comment across all accessible projects, newest-first (paginated) +- get_everything_completed_cards (RO, paginated): Completed cards across all accessible projects, grouped by project (paginated) +- get_everything_completed_todos (RO, paginated): Completed to-dos across all accessible projects, grouped by project (paginated) +- get_everything_files (RO, paginated): Get every file recording across all accessible projects, newest-first (paginated) +- get_everything_forwards (RO, paginated): Get every inbox forward across all accessible projects, newest-first (paginated) +- get_everything_messages (RO, paginated): Get every message across all accessible projects, newest-first (paginated) +- get_everything_no_due_date_cards (RO, paginated): Open cards with no due date across all accessible projects, grouped by project (paginated) +- get_everything_no_due_date_todos (RO, paginated): Open to-dos with no due date across all accessible projects, grouped by project (paginated) +- get_everything_not_now_cards (RO, paginated): Cards parked in a project's "Not now" column across all accessible projects, grouped by project (paginated) +- get_everything_open_cards (RO, paginated): Incomplete cards in active columns across all accessible projects, grouped by project (paginated) +- get_everything_open_todos (RO, paginated): Active, incomplete to-dos across all accessible projects, grouped by project (paginated) +- get_everything_overdue_cards (RO): Get every overdue card across all accessible projects, oldest-due-date-first +- get_everything_overdue_todos (RO): Get every overdue to-do across all accessible projects, oldest-due-date-first +- get_everything_unassigned_cards (RO, paginated): Open, unassigned cards across all accessible projects, grouped by project (paginated) +- get_everything_unassigned_todos (RO, paginated): Open, unassigned to-dos across all accessible projects, grouped by project (paginated) + +== basecamp_clientside +The Clientside: client approvals, correspondences, replies, and client visibility of recordings. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- get_client_approval (RO): Get a single client approval by id +- get_client_correspondence (RO): Get a single client correspondence by id +- get_client_reply (RO): Get a single client reply by id +- list_client_approvals (RO, paginated): List all client approvals in a project +- list_client_correspondences (RO, paginated): List all client correspondences in a project +- list_client_replies (RO, paginated): List all client replies for a recording (correspondence or approval) +- set_client_visibility: Set client visibility for a recording + +== basecamp_forwards +Email forwards: project inboxes, forwarded emails, and their replies. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- get_forward (RO): Get a forward by ID +- get_forward_reply (RO): Get a forward reply by ID +- get_inbox (RO): Get an inbox by ID +- list_forward_replies (RO, paginated): List all replies to a forward +- list_forwards (RO, paginated): List all forwards in an inbox + +== basecamp_account +Account info and your personal surface: gauges and needles, my assignments and priorities, notifications and bubble-ups, bookmarks, personal note, and drafts. + +Gateway tool: call with {"action": "...", "params": {...}}. +Call {"action": "describe", "params": {"action": "NAME"}} for an action's full parameter schema. + +ACTIONS (RO = read-only): +- create_bookmark: Bookmark a recording for the current user +- create_gauge_needle: Create a gauge needle (progress update) for a project +- delete_bookmark: Remove the current user's bookmark from a recording (returns 204 No Content) +- deprioritize_assignment: Remove a recording from Up Next (returns 204 No Content) +- destroy_gauge_needle: Destroy a gauge needle +- get_account (RO): Get the account for the current access token +- get_bookmark (RO): Report whether the current user has bookmarked the recording +- get_bubble_ups (RO, paginated): Get the current user's current and scheduled bubble-ups (paginated, 50 per page) +- get_gauge_needle (RO): Get a gauge needle by ID +- get_my_assignments (RO): Get the current user's active assignments grouped into priorities and non_priorities +- get_my_completed_assignments (RO): Get the current user's completed assignments +- get_my_due_assignments (RO): Get the current user's assignments filtered by due date scope +- get_my_note (RO): Get the authenticated user's note — a per-person notebook singleton at /my/notes.json +- get_my_notifications (RO): Get the current user's notification inbox (the "Hey!" menu) +- list_gauge_needles (RO, paginated): List gauge needles for a project, ordered newest first +- list_gauges (RO, paginated): List gauges across all projects the authenticated user has access to +- list_my_bookmarks (RO, paginated): List the current user's bookmarks, most recently bookmarked first (paginated) +- list_my_drafts (RO, paginated): List the current user's drafts across their active projects, most recently updated first (paginated, capped at 250 like /my/assignments) +- mark_as_read: Mark specified items as read +- prioritize_assignment: Add a recording to Up Next — the current user's ordered list of prioritized assignments (the priorities returned by GetMyAssignments) +- remove_account_logo: Remove the account logo +- reorder_up_next: Move an already-prioritized recording to a new 1-based position in Up Next (returns 204 No Content) +- toggle_gauge: Enable or disable the gauge for a project +- update_account_name: Rename the current account +- update_gauge_needle: Update a gauge needle's description +- update_my_note: Replace the note's content, recording a new revision server-side + diff --git a/nix/package.nix b/nix/package.nix index bb918500..6f4ca1db 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -8,7 +8,7 @@ buildGoModule.override { go = go_1_26; } (finalAttrs: { src = lib.cleanSource ./..; # To update: set to lib.fakeHash, run `nix build`, use the hash from the error. - vendorHash = "sha256-6TAf9LSEQ8AEYAzFLCHN3mHT8gGoOGQ5zcsy55P2luc="; + vendorHash = "sha256-MZdTSzvWAAbFpK+hb3mBi1A74nFbj79jK10btp3GXYM="; subPackages = [ "cmd/basecamp" ]; diff --git a/scripts/sync-mcp-model.sh b/scripts/sync-mcp-model.sh new file mode 100755 index 00000000..310bc76b --- /dev/null +++ b/scripts/sync-mcp-model.sh @@ -0,0 +1,142 @@ +#!/bin/sh +# Sync the vendored basecamp-sdk model snapshot that MCP catalog generation reads. +# +# The catalog derives from basecamp-sdk's behavior model (per-operation traits: +# readonly, idempotent, pagination, retry) joined with its exported OpenAPI +# spec (operationId, method, path, tags, docs, parameter schemas). Both files +# are build products of basecamp-sdk's Smithy model, so we vendor a snapshot +# here rather than parse Smithy ourselves: CI stays hermetic and the reviewed +# diff shows exactly which surface changed when the SDK moves. Keep the +# snapshot in lockstep with the basecamp-sdk version pinned in go.mod — +# TestCatalogModelProvenance enforces it. +# +# Tag patch: the SDK export leaves a handful of operations untagged, and the +# toolkit catalog joins operations to domains by tag (exactly one per +# operation, refused otherwise). Until the tags land upstream in the Smithy +# model, this script assigns them here — the patch tables below are the whole +# divergence from the upstream export, applied on copy and recorded in +# PROVENANCE.json so a future SDK release that tags them upstream shows up as +# a sync-time conflict rather than a silent double-tag. +# +# Exclusions: raw-binary upload operations (multipart/octet-stream bodies) +# can't ride the JSON tool-call convention, and the toolkit refuses non-JSON +# bodies at load, so they are dropped from both model files here. Uploads +# stay a CLI affair (basecamp attach / upload). +# +# Usage: scripts/sync-mcp-model.sh [path-to-basecamp-sdk-checkout] +set -eu + +sdk="${1:-../basecamp-sdk}" +dest="$(dirname "$0")/../internal/mcpserver/model" + +# Resolve provenance before touching the destination, so a checkout that is +# not a git repo (or otherwise broken) can't leave a torn snapshot behind. +# --dirty marks a checkout with uncommitted changes, so modified model files +# are never recorded as a clean release tag. +commit=$(git -C "$sdk" rev-parse HEAD) +ref=$(git -C "$sdk" describe --tags --always --dirty) + +for f in behavior-model.json openapi.json; do + [ -f "$sdk/$f" ] || { echo "missing $sdk/$f (pass a basecamp-sdk checkout path)" >&2; exit 1; } +done + +# Copy both model files, assigning tags to the operations the export leaves +# untagged and dropping the raw-binary upload operations. Refuses an +# operation that grew an upstream tag (drop it from the table then) and +# refuses any untagged operation the table doesn't cover, so the patches can +# only shrink as upstream catches up. +python3 - "$sdk" "$dest" <<'PY' +import json, sys + +EXCLUDED_OPERATIONS = { + "CreateAttachment", + "CreateCampfireUpload", + "UpdateAccountLogo", +} + +PATCHED_TAGS = { + "GetAnswersByPerson": "Automation", + "GetQuestionReminders": "Automation", + "ListQuestionAnswerers": "Automation", + "PauseQuestion": "Automation", + "ResumeQuestion": "Automation", + "UpdateQuestionNotificationSettings": "Automation", + "RepositionTodo": "Todos", + "SubscribeToCardColumn": "Card Tables", + "UnsubscribeFromCardColumn": "Card Tables", + "GetAssignedTodos": "Reports", + "GetOverdueTodos": "Reports", + "GetPersonProgress": "Reports", + "GetProgressReport": "Reports", + "GetProjectTimeline": "Reports", + "GetUpcomingSchedule": "Reports", + "ListAssignablePeople": "Reports", +} + +sdk, dest = sys.argv[1], sys.argv[2] +with open(f"{sdk}/openapi.json") as f: + doc = json.load(f) + +patched = [] +excluded = [] +for path in list(doc["paths"]): + methods = doc["paths"][path] + for method in list(methods): + op = methods[method] + if not isinstance(op, dict) or "operationId" not in op: + continue + oid = op["operationId"] + if oid in EXCLUDED_OPERATIONS: + body = op.get("requestBody", {}).get("content", {}) + if "application/json" in body or not body: + sys.exit(f"{oid} takes a JSON body now — drop it from EXCLUDED_OPERATIONS") + del methods[method] + excluded.append(oid) + continue + tags = op.get("tags") or [] + if oid in PATCHED_TAGS: + if tags: + sys.exit(f"{oid} is tagged {tags} upstream now — drop it from PATCHED_TAGS") + op["tags"] = [PATCHED_TAGS[oid]] + patched.append(oid) + elif not tags: + sys.exit(f"{oid} is untagged and not in PATCHED_TAGS — assign it a domain tag") + if not methods: + del doc["paths"][path] + +missing = sorted(set(PATCHED_TAGS) - set(patched)) +if missing: + sys.exit(f"PATCHED_TAGS entries not found in the export: {missing}") +missing = sorted(set(EXCLUDED_OPERATIONS) - set(excluded)) +if missing: + sys.exit(f"EXCLUDED_OPERATIONS entries not found in the export: {missing}") + +with open(f"{dest}/openapi.json", "w") as f: + json.dump(doc, f, indent=2) + f.write("\n") + +# The catalog join is strict both ways, so the excluded operations leave the +# behavior model too. +with open(f"{sdk}/behavior-model.json") as f: + bm = json.load(f) +for oid in EXCLUDED_OPERATIONS: + bm["operations"].pop(oid, None) +with open(f"{dest}/behavior-model.json", "w") as f: + json.dump(bm, f, indent=2) + f.write("\n") + +print(f"patched tags onto {len(patched)} untagged operations; excluded {len(excluded)} binary-upload operations") +PY + +cat > "$dest/PROVENANCE.json" <