basecamp-sdk 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +106 -10
- data/lib/basecamp/client.rb +8 -8
- data/lib/basecamp/config.rb +4 -3
- data/lib/basecamp/generated/metadata.json +7 -17
- data/lib/basecamp/generated/services/account_service.rb +1 -1
- data/lib/basecamp/generated/services/automation_service.rb +1 -1
- data/lib/basecamp/generated/services/boosts_service.rb +3 -3
- data/lib/basecamp/generated/services/campfires_service.rb +7 -7
- data/lib/basecamp/generated/services/card_columns_service.rb +1 -1
- data/lib/basecamp/generated/services/card_steps_service.rb +1 -1
- data/lib/basecamp/generated/services/card_tables_service.rb +1 -1
- data/lib/basecamp/generated/services/cards_service.rb +2 -2
- data/lib/basecamp/generated/services/checkins_service.rb +8 -8
- data/lib/basecamp/generated/services/client_approvals_service.rb +2 -2
- data/lib/basecamp/generated/services/client_correspondences_service.rb +2 -2
- data/lib/basecamp/generated/services/client_replies_service.rb +2 -2
- data/lib/basecamp/generated/services/comments_service.rb +2 -2
- data/lib/basecamp/generated/services/documents_service.rb +2 -2
- data/lib/basecamp/generated/services/events_service.rb +1 -1
- data/lib/basecamp/generated/services/everything_service.rb +16 -26
- data/lib/basecamp/generated/services/forwards_service.rb +5 -5
- data/lib/basecamp/generated/services/gauges_service.rb +3 -3
- data/lib/basecamp/generated/services/hill_charts_service.rb +1 -1
- data/lib/basecamp/generated/services/message_boards_service.rb +1 -1
- data/lib/basecamp/generated/services/message_types_service.rb +2 -2
- data/lib/basecamp/generated/services/messages_service.rb +2 -2
- data/lib/basecamp/generated/services/my_assignments_service.rb +3 -3
- data/lib/basecamp/generated/services/my_notifications_service.rb +2 -2
- data/lib/basecamp/generated/services/people_service.rb +8 -8
- data/lib/basecamp/generated/services/projects_service.rb +2 -2
- data/lib/basecamp/generated/services/recordings_service.rb +2 -2
- data/lib/basecamp/generated/services/reports_service.rb +5 -5
- data/lib/basecamp/generated/services/schedules_service.rb +4 -4
- data/lib/basecamp/generated/services/search_service.rb +2 -2
- data/lib/basecamp/generated/services/subscriptions_service.rb +1 -1
- data/lib/basecamp/generated/services/templates_service.rb +3 -3
- data/lib/basecamp/generated/services/timeline_service.rb +1 -1
- data/lib/basecamp/generated/services/timesheets_service.rb +4 -4
- data/lib/basecamp/generated/services/todolist_groups_service.rb +1 -1
- data/lib/basecamp/generated/services/todolists_service.rb +2 -2
- data/lib/basecamp/generated/services/todos_service.rb +2 -2
- data/lib/basecamp/generated/services/todosets_service.rb +1 -1
- data/lib/basecamp/generated/services/tools_service.rb +1 -1
- data/lib/basecamp/generated/services/uploads_service.rb +3 -3
- data/lib/basecamp/generated/services/vaults_service.rb +2 -2
- data/lib/basecamp/generated/services/webhooks_service.rb +2 -2
- data/lib/basecamp/generated/types.rb +1 -34
- data/lib/basecamp/http.rb +47 -19
- data/lib/basecamp/oauth/device_authorization.rb +37 -0
- data/lib/basecamp/oauth/device_flow.rb +873 -0
- data/lib/basecamp/oauth/device_flow_error.rb +46 -0
- data/lib/basecamp/oauth/discovery.rb +13 -11
- data/lib/basecamp/oauth/exchange.rb +44 -5
- data/lib/basecamp/oauth/fetcher.rb +626 -56
- data/lib/basecamp/oauth/oauth_error.rb +3 -1
- data/lib/basecamp/oauth/refresh_request.rb +8 -2
- data/lib/basecamp/oauth/resource.rb +13 -10
- data/lib/basecamp/oauth/token.rb +10 -3
- data/lib/basecamp/oauth.rb +47 -2
- data/lib/basecamp/version.rb +2 -2
- data/scripts/generate-services.rb +9 -6
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2b13ea2c32e1d4bf22a04af06e8c94d039e7698643a7aa7d9e0813f1526db2d
|
|
4
|
+
data.tar.gz: 942a8f09b4950764aa132ebd52a195d9d0eebb27b93656fb542e7e52b20ae1af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '081775bc028c20205c904d026ab9f79bf077e7cde1d1bf5dd074b0e641fedf2ac47ebe3ca3cd6860f5bed05ad92d672abcaaf0332ec0f9aa94327ebc4cfcb130'
|
|
7
|
+
data.tar.gz: ffee6cf6e25a9ad523628b5d5f9da23f15460c3b01cd03dd8ab5df472942826e05d1a40afc09fc6cec9acaf9a3b2329c468f94b69a3ee4f95e2a784d94eb2775
|
data/README.md
CHANGED
|
@@ -42,7 +42,6 @@ project = account.projects.get(project_id: 12345)
|
|
|
42
42
|
|
|
43
43
|
# Create a todo
|
|
44
44
|
todo = account.todos.create(
|
|
45
|
-
project_id: 12345,
|
|
46
45
|
todolist_id: 67890,
|
|
47
46
|
content: "Review PR",
|
|
48
47
|
due_on: "2024-12-31"
|
|
@@ -57,7 +56,7 @@ todo = account.todos.create(
|
|
|
57
56
|
config = Basecamp::Config.new(
|
|
58
57
|
base_url: "https://3.basecampapi.com", # Default
|
|
59
58
|
timeout: 30, # Request timeout in seconds
|
|
60
|
-
max_retries: 3, #
|
|
59
|
+
max_retries: 3, # Total request attempts for GET requests (including the initial request)
|
|
61
60
|
base_delay: 1.0, # Base delay for exponential backoff
|
|
62
61
|
max_pages: 10_000 # Max pages for pagination
|
|
63
62
|
)
|
|
@@ -72,7 +71,7 @@ client = Basecamp::Client.new(config: config, token_provider: token_provider)
|
|
|
72
71
|
|--------|---------|-------------|
|
|
73
72
|
| `base_url` | `https://3.basecampapi.com` | Basecamp API base URL |
|
|
74
73
|
| `timeout` | `30` | HTTP request timeout (seconds) |
|
|
75
|
-
| `max_retries` | `3` |
|
|
74
|
+
| `max_retries` | `3` | Total request attempts for GET requests (including the initial request) |
|
|
76
75
|
| `base_delay` | `1.0` | Base delay for exponential backoff (seconds) |
|
|
77
76
|
| `max_jitter` | `0.1` | Maximum random jitter added to delays |
|
|
78
77
|
| `max_pages` | `10_000` | Maximum pages to fetch during pagination |
|
|
@@ -133,6 +132,99 @@ if token.expired?
|
|
|
133
132
|
end
|
|
134
133
|
```
|
|
135
134
|
|
|
135
|
+
### Device Authorization Grant (RFC 8628)
|
|
136
|
+
|
|
137
|
+
For input-constrained clients (CLIs, TVs) that can't host a redirect URI, the
|
|
138
|
+
device flow trades a redirect for a user-entered code. Basecamp pre-registers the
|
|
139
|
+
public `basecamp-cli` client (`token_endpoint_auth_method: none`, no secret); an
|
|
140
|
+
omitted scope defaults to `read` — prefer pinning it explicitly with
|
|
141
|
+
`scope: "read"`. Pass bare origins everywhere (no trailing slash): binding is
|
|
142
|
+
code-point exact — a trailing-slash `expected_issuer` raises a **hard**
|
|
143
|
+
`expected_issuer_unavailable`, while a trailing-slash resource origin breaks
|
|
144
|
+
the hop-1 binding and silently soft-falls back to Launchpad.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# The device grant runs against an ALREADY-SELECTED config whose issuer advertises
|
|
148
|
+
# a device_authorization_endpoint. Resolve one with resource-first discovery —
|
|
149
|
+
# NOT discover_launchpad: Launchpad advertises no device endpoint, so the
|
|
150
|
+
# capability guard would reject it.
|
|
151
|
+
result = Basecamp::Oauth.discover_from_resource("https://3.basecampapi.com")
|
|
152
|
+
raise "device login is not available for this resource" unless result.selected?
|
|
153
|
+
config = result.config
|
|
154
|
+
|
|
155
|
+
# One call runs the whole grant against the SELECTED config: it guards
|
|
156
|
+
# capability, requests a device/user code, shows it via the display hook, then
|
|
157
|
+
# polls the token endpoint until the user approves.
|
|
158
|
+
token = Basecamp::Oauth.perform_device_login(
|
|
159
|
+
config: config,
|
|
160
|
+
client_id: "basecamp-cli",
|
|
161
|
+
display: ->(auth) do
|
|
162
|
+
puts "Visit #{auth.verification_uri} and enter code: #{auth.user_code}"
|
|
163
|
+
puts "Or open: #{auth.verification_uri_complete}" if auth.verification_uri_complete
|
|
164
|
+
end
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
client = Basecamp.client(access_token: token.access_token)
|
|
168
|
+
|
|
169
|
+
# BC5 device logins as basecamp-cli mint MULTI-ACCOUNT refresh tokens: the
|
|
170
|
+
# token carries an RFC 8707 resource indicator (token.resource,
|
|
171
|
+
# "urn:bc:account:<id>"), and refreshing without echoing it is rejected
|
|
172
|
+
# (400 invalid_request). Persist token.resource alongside the tokens and
|
|
173
|
+
# echo it on refresh:
|
|
174
|
+
# A device-token response MAY omit refresh_token — guard it: without one,
|
|
175
|
+
# refreshing is impossible and the user must re-run the device login.
|
|
176
|
+
if token.expired? && token.refresh_token
|
|
177
|
+
fresh = Basecamp::Oauth.refresh_token(
|
|
178
|
+
token_endpoint: config.token_endpoint,
|
|
179
|
+
refresh_token: token.refresh_token,
|
|
180
|
+
client_id: "basecamp-cli", # public client — no secret
|
|
181
|
+
resource: token.resource
|
|
182
|
+
)
|
|
183
|
+
# A refresh response MAY omit resource (the binding is unchanged) — persist
|
|
184
|
+
# `fresh.resource || token.resource` so the next refresh still echoes it.
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The capability guard requires BOTH `config.device_authorization_endpoint` AND the
|
|
189
|
+
exact grant-type URN `urn:ietf:params:oauth:grant-type:device_code` in
|
|
190
|
+
`config.grant_types_supported` — servers advertise the full URN, so checking for
|
|
191
|
+
a bare `device_code` entry would wrongly conclude the flow is unavailable;
|
|
192
|
+
otherwise it raises
|
|
193
|
+
`Basecamp::Oauth::DeviceFlowError` with `reason: :unavailable` before any request
|
|
194
|
+
is issued. The two lower-level steps are also exposed directly:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
auth = Basecamp::Oauth.request_device_authorization(
|
|
198
|
+
device_authorization_endpoint: config.device_authorization_endpoint,
|
|
199
|
+
client_id: "basecamp-cli"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
token = Basecamp::Oauth.poll_device_token(
|
|
203
|
+
token_endpoint: config.token_endpoint,
|
|
204
|
+
client_id: "basecamp-cli",
|
|
205
|
+
device_code: auth.device_code,
|
|
206
|
+
interval: auth.interval,
|
|
207
|
+
expires_in: auth.expires_in
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The polling loop waits at least `interval` seconds between polls against a
|
|
212
|
+
**monotonic** deadline, sustains a `slow_down` bump (+5s for every later poll),
|
|
213
|
+
and backs off exponentially on connection timeouts. A terminal outcome raises
|
|
214
|
+
`DeviceFlowError` carrying a `reason` whose parent `type` is derived from it:
|
|
215
|
+
|
|
216
|
+
| `reason` | `type` | Meaning |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `:access_denied` | `auth` | The user declined the request |
|
|
219
|
+
| `:expired` | `auth` | The code expired before approval |
|
|
220
|
+
| `:transport` | `network` (retryable) | A network failure ended the flow |
|
|
221
|
+
| `:unavailable` | `validation` | The config can't do device flow |
|
|
222
|
+
| `:cancelled` | `usage` | The caller cancelled the flow |
|
|
223
|
+
|
|
224
|
+
`poll_device_token` and `perform_device_login` accept injectable `clock`,
|
|
225
|
+
`sleeper`, and `cancelled` callables — pass a `cancelled` probe (e.g. one that
|
|
226
|
+
checks a signal set by SIGINT) to abort a pending login cleanly.
|
|
227
|
+
|
|
136
228
|
### Resource-First Discovery (RFC 9728 + RFC 8414)
|
|
137
229
|
|
|
138
230
|
BC5's Authorization Server (AS) metadata lives only at the canonical issuer (the
|
|
@@ -179,7 +271,7 @@ aborts before an oversized body is buffered.
|
|
|
179
271
|
|
|
180
272
|
## Services
|
|
181
273
|
|
|
182
|
-
The SDK provides
|
|
274
|
+
The SDK provides 46 account-scoped services. The table below covers the common ones; see `lib/basecamp/generated/services/` for the authoritative, complete set:
|
|
183
275
|
|
|
184
276
|
| Service | Description |
|
|
185
277
|
|---------|-------------|
|
|
@@ -260,13 +352,15 @@ result = account.download_url(url)
|
|
|
260
352
|
|
|
261
353
|
## Retry Behavior
|
|
262
354
|
|
|
263
|
-
GET requests automatically retry
|
|
264
|
-
|
|
265
|
-
- **Retryable errors**: 429 (rate limit), 502, 503, 504 (gateway errors)
|
|
266
|
-
- **Backoff**: Exponential with jitter (1s, 2s, 4s...)
|
|
267
|
-
- **Rate limits**: Respects `Retry-After` header
|
|
355
|
+
Only plain GET requests retry — automatically, with exponential backoff. Mutation operations (POST, PUT, DELETE) do **not** retry to prevent data duplication, and the raw upload and download paths skip the retry loop entirely (the upload path is strictly one request; the download hop keeps only the one-shot 401 replay below).
|
|
268
356
|
|
|
269
|
-
|
|
357
|
+
- **Which errors**: Retry keys off the error's `retryable?` classification, not a declared status list — 429 (rate limit), 500, 502, 503, 504, and any other 5xx all retry, as does `NetworkError` (connection failures, including DNS and connect-phase timeouts). Read timeouts are the exception: Faraday surfaces them as a status-less `ApiError` with `retryable? == false`, so a GET that times out mid-response fails on the first attempt. 400, 401, 403, 404, and 422 never retry.
|
|
358
|
+
- **`max_retries`**: Total request attempts for GET requests, including the initial request — the default `3` means one initial attempt plus two retries. **`max_retries: 0` sends zero requests** and raises `Basecamp::ApiError` (`"Request failed after 0 attempts"`).
|
|
359
|
+
- **Backoff**: Exponential with jitter — `base_delay * 2^(attempt - 1) + rand * max_jitter` — uncapped, bounded in practice by the attempt budget.
|
|
360
|
+
- **Rate limits**: A 429's `Retry-After` header overrides the calculated backoff. Only 429 carries it: 5xx and network errors always use the exponential backoff.
|
|
361
|
+
- **401 responses**: With a refresh-capable token provider, the SDK refreshes the token and replays the request **once** — for all methods, including mutations — outside the `max_retries` budget. A second 401 is surfaced. The raw upload path has no 401 replay.
|
|
362
|
+
- **Per-operation metadata**: The retry policy operations declare (`retry_on` statuses, per-operation `max`) is inert in Ruby — every API GET issued through the client, including the Launchpad authorization fetch, rides the same classification-based loop bounded by `config.max_retries` alone. (The download flow's redirect hop and OAuth discovery use their own single-attempt transports.)
|
|
363
|
+
- **`retryable?`**: Unlike SDKs where the error classification is only a hint for your own code, in Ruby an error's `retryable?` (and `retry_after`) is exactly what the transport acts on for GET requests.
|
|
270
364
|
|
|
271
365
|
## Error Handling
|
|
272
366
|
|
|
@@ -335,6 +429,8 @@ client = Basecamp::Client.new(
|
|
|
335
429
|
| `BASECAMP_TOKEN` | OAuth access token |
|
|
336
430
|
| `BASECAMP_ACCOUNT_ID` | Account ID |
|
|
337
431
|
| `BASECAMP_BASE_URL` | API base URL (default: `https://3.basecampapi.com`) |
|
|
432
|
+
| `BASECAMP_TIMEOUT` | Request timeout in seconds (default: `30`) |
|
|
433
|
+
| `BASECAMP_MAX_RETRIES` | Total request attempts for GET requests, including the initial request (default: `3`) |
|
|
338
434
|
|
|
339
435
|
## Development
|
|
340
436
|
|
data/lib/basecamp/client.rb
CHANGED
|
@@ -152,8 +152,8 @@ module Basecamp
|
|
|
152
152
|
# @param path [String] URL path (without account prefix)
|
|
153
153
|
# @param params [Hash] query parameters
|
|
154
154
|
# @return [Response]
|
|
155
|
-
def get(path, params: {})
|
|
156
|
-
@parent.http.get(account_path(path), params: params)
|
|
155
|
+
def get(path, params: {}, operation: nil)
|
|
156
|
+
@parent.http.get(account_path(path), params: params, operation: operation)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
# Performs a POST request scoped to this account.
|
|
@@ -204,8 +204,8 @@ module Basecamp
|
|
|
204
204
|
# @param params [Hash] query parameters
|
|
205
205
|
# @yield [Hash] each item from the response
|
|
206
206
|
# @return [Enumerator] if no block given
|
|
207
|
-
def paginate(path, params: {}, &)
|
|
208
|
-
@parent.http.paginate(account_path(path), params: params, &)
|
|
207
|
+
def paginate(path, params: {}, operation: nil, &)
|
|
208
|
+
@parent.http.paginate(account_path(path), params: params, operation: operation, &)
|
|
209
209
|
end
|
|
210
210
|
|
|
211
211
|
# Fetches all pages of a paginated resource, extracting items from a key.
|
|
@@ -215,8 +215,8 @@ module Basecamp
|
|
|
215
215
|
# @param params [Hash] query parameters
|
|
216
216
|
# @yield [Hash] each item from the response
|
|
217
217
|
# @return [Enumerator] if no block given
|
|
218
|
-
def paginate_key(path, key:, params: {}, &)
|
|
219
|
-
@parent.http.paginate_key(account_path(path), key: key, params: params, &)
|
|
218
|
+
def paginate_key(path, key:, params: {}, operation: nil, &)
|
|
219
|
+
@parent.http.paginate_key(account_path(path), key: key, params: params, operation: operation, &)
|
|
220
220
|
end
|
|
221
221
|
|
|
222
222
|
# Fetches a wrapped paginated resource, returning wrapper fields + lazy paginated items.
|
|
@@ -224,8 +224,8 @@ module Basecamp
|
|
|
224
224
|
# @param key [String] the key containing the array of paginated items
|
|
225
225
|
# @param params [Hash] query parameters
|
|
226
226
|
# @return [Hash] wrapper fields merged with key => Enumerator of all items
|
|
227
|
-
def paginate_wrapped(path, key:, params: {})
|
|
228
|
-
@parent.http.paginate_wrapped(account_path(path), key: key, params: params)
|
|
227
|
+
def paginate_wrapped(path, key:, params: {}, operation: nil)
|
|
228
|
+
@parent.http.paginate_wrapped(account_path(path), key: key, params: params, operation: operation)
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
# Downloads file content from any API-routable download URL.
|
data/lib/basecamp/config.rb
CHANGED
|
@@ -24,7 +24,8 @@ module Basecamp
|
|
|
24
24
|
# @return [Integer] request timeout in seconds
|
|
25
25
|
attr_accessor :timeout
|
|
26
26
|
|
|
27
|
-
# @return [Integer]
|
|
27
|
+
# @return [Integer] total request attempts for GET requests, including the
|
|
28
|
+
# initial request (0 sends no requests at all and raises)
|
|
28
29
|
attr_accessor :max_retries
|
|
29
30
|
|
|
30
31
|
# @return [Float] initial backoff delay in seconds
|
|
@@ -48,7 +49,7 @@ module Basecamp
|
|
|
48
49
|
#
|
|
49
50
|
# @param base_url [String] API base URL
|
|
50
51
|
# @param timeout [Integer] request timeout in seconds
|
|
51
|
-
# @param max_retries [Integer]
|
|
52
|
+
# @param max_retries [Integer] total request attempts for GET requests, including the initial request
|
|
52
53
|
# @param base_delay [Float] initial backoff delay
|
|
53
54
|
# @param max_jitter [Float] maximum jitter
|
|
54
55
|
# @param max_pages [Integer] maximum pages to fetch
|
|
@@ -78,7 +79,7 @@ module Basecamp
|
|
|
78
79
|
# Environment variables:
|
|
79
80
|
# - BASECAMP_BASE_URL: API base URL
|
|
80
81
|
# - BASECAMP_TIMEOUT: Request timeout in seconds
|
|
81
|
-
# - BASECAMP_MAX_RETRIES:
|
|
82
|
+
# - BASECAMP_MAX_RETRIES: Total request attempts for GET requests, including the initial request
|
|
82
83
|
#
|
|
83
84
|
# @return [Config]
|
|
84
85
|
def self.from_env
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://basecamp.com/schemas/sdk-metadata.json",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"generated": "2026-07-
|
|
4
|
+
"generated": "2026-07-31T06:58:29Z",
|
|
5
5
|
"operations": {
|
|
6
6
|
"GetAccount": {
|
|
7
7
|
"retry": {
|
|
@@ -67,22 +67,6 @@
|
|
|
67
67
|
]
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"GetEverythingBoosts": {
|
|
71
|
-
"retry": {
|
|
72
|
-
"maxAttempts": 3,
|
|
73
|
-
"baseDelayMs": 1000,
|
|
74
|
-
"backoff": "exponential",
|
|
75
|
-
"retryOn": [
|
|
76
|
-
429,
|
|
77
|
-
503
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
"pagination": {
|
|
81
|
-
"style": "link",
|
|
82
|
-
"totalCountHeader": "X-Total-Count",
|
|
83
|
-
"maxPageSize": 50
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
70
|
"GetBoost": {
|
|
87
71
|
"retry": {
|
|
88
72
|
"maxAttempts": 3,
|
|
@@ -131,6 +115,9 @@
|
|
|
131
115
|
429,
|
|
132
116
|
503
|
|
133
117
|
]
|
|
118
|
+
},
|
|
119
|
+
"idempotent": {
|
|
120
|
+
"natural": true
|
|
134
121
|
}
|
|
135
122
|
},
|
|
136
123
|
"DisableCardColumnOnHold": {
|
|
@@ -2054,6 +2041,9 @@
|
|
|
2054
2041
|
429,
|
|
2055
2042
|
503
|
|
2056
2043
|
]
|
|
2044
|
+
},
|
|
2045
|
+
"idempotent": {
|
|
2046
|
+
"natural": true
|
|
2057
2047
|
}
|
|
2058
2048
|
},
|
|
2059
2049
|
"UpdateSubscription": {
|
|
@@ -11,7 +11,7 @@ module Basecamp
|
|
|
11
11
|
# @return [Hash] response data
|
|
12
12
|
def get_account()
|
|
13
13
|
with_operation(service: "account", operation: "get_account", is_mutation: false) do
|
|
14
|
-
http_get("/account.json").json
|
|
14
|
+
http_get("/account.json", operation: "GetAccount").json
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@ module Basecamp
|
|
|
11
11
|
# @return [Array<Hash>] response data
|
|
12
12
|
def list_lineup_markers()
|
|
13
13
|
with_operation(service: "automation", operation: "list_lineup_markers", is_mutation: false) do
|
|
14
|
-
http_get("/lineup/markers.json").json
|
|
14
|
+
http_get("/lineup/markers.json", operation: "ListLineupMarkers").json
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Hash] response data
|
|
13
13
|
def get_boost(boost_id:)
|
|
14
14
|
with_operation(service: "boosts", operation: "get_boost", is_mutation: false, resource_id: boost_id) do
|
|
15
|
-
http_get("/boosts/#{boost_id}").json
|
|
15
|
+
http_get("/boosts/#{boost_id}", operation: "GetBoost").json
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -31,7 +31,7 @@ module Basecamp
|
|
|
31
31
|
# @return [Enumerator<Hash>] paginated results
|
|
32
32
|
def list_recording_boosts(recording_id:)
|
|
33
33
|
wrap_paginated(service: "boosts", operation: "list_recording_boosts", is_mutation: false, resource_id: recording_id) do
|
|
34
|
-
paginate("/recordings/#{recording_id}/boosts.json")
|
|
34
|
+
paginate("/recordings/#{recording_id}/boosts.json", operation: "ListRecordingBoosts")
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -51,7 +51,7 @@ module Basecamp
|
|
|
51
51
|
# @return [Enumerator<Hash>] paginated results
|
|
52
52
|
def list_event_boosts(recording_id:, event_id:)
|
|
53
53
|
wrap_paginated(service: "boosts", operation: "list_event_boosts", is_mutation: false, resource_id: event_id) do
|
|
54
|
-
paginate("/recordings/#{recording_id}/events/#{event_id}/boosts.json")
|
|
54
|
+
paginate("/recordings/#{recording_id}/events/#{event_id}/boosts.json", operation: "ListEventBoosts")
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -13,7 +13,7 @@ module Basecamp
|
|
|
13
13
|
# @return [Enumerator<Hash>] paginated results
|
|
14
14
|
def list()
|
|
15
15
|
wrap_paginated(service: "campfires", operation: "list", is_mutation: false) do
|
|
16
|
-
paginate("/chats.json")
|
|
16
|
+
paginate("/chats.json", operation: "ListCampfires")
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ module Basecamp
|
|
|
22
22
|
# @return [Hash] response data
|
|
23
23
|
def get(campfire_id:)
|
|
24
24
|
with_operation(service: "campfires", operation: "get", is_mutation: false, resource_id: campfire_id) do
|
|
25
|
-
http_get("/chats/#{campfire_id}").json
|
|
25
|
+
http_get("/chats/#{campfire_id}", operation: "GetCampfire").json
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -31,7 +31,7 @@ module Basecamp
|
|
|
31
31
|
# @return [Enumerator<Hash>] paginated results
|
|
32
32
|
def list_chatbots(campfire_id:)
|
|
33
33
|
wrap_paginated(service: "campfires", operation: "list_chatbots", is_mutation: false, resource_id: campfire_id) do
|
|
34
|
-
paginate("/chats/#{campfire_id}/integrations.json")
|
|
34
|
+
paginate("/chats/#{campfire_id}/integrations.json", operation: "ListChatbots")
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -52,7 +52,7 @@ module Basecamp
|
|
|
52
52
|
# @return [Hash] response data
|
|
53
53
|
def get_chatbot(campfire_id:, chatbot_id:)
|
|
54
54
|
with_operation(service: "campfires", operation: "get_chatbot", is_mutation: false, resource_id: chatbot_id) do
|
|
55
|
-
http_get("/chats/#{campfire_id}/integrations/#{chatbot_id}").json
|
|
55
|
+
http_get("/chats/#{campfire_id}/integrations/#{chatbot_id}", operation: "GetChatbot").json
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -87,7 +87,7 @@ module Basecamp
|
|
|
87
87
|
def list_lines(campfire_id:, sort: nil, direction: nil)
|
|
88
88
|
wrap_paginated(service: "campfires", operation: "list_lines", is_mutation: false, resource_id: campfire_id) do
|
|
89
89
|
params = compact_query_params(sort: sort, direction: direction)
|
|
90
|
-
paginate("/chats/#{campfire_id}/lines.json", params: params)
|
|
90
|
+
paginate("/chats/#{campfire_id}/lines.json", params: params, operation: "ListCampfireLines")
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
@@ -108,7 +108,7 @@ module Basecamp
|
|
|
108
108
|
# @return [Hash] response data
|
|
109
109
|
def get_line(campfire_id:, line_id:)
|
|
110
110
|
with_operation(service: "campfires", operation: "get_line", is_mutation: false, resource_id: line_id) do
|
|
111
|
-
http_get("/chats/#{campfire_id}/lines/#{line_id}").json
|
|
111
|
+
http_get("/chats/#{campfire_id}/lines/#{line_id}", operation: "GetCampfireLine").json
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
|
|
@@ -143,7 +143,7 @@ module Basecamp
|
|
|
143
143
|
def list_uploads(campfire_id:, sort: nil, direction: nil)
|
|
144
144
|
wrap_paginated(service: "campfires", operation: "list_uploads", is_mutation: false, resource_id: campfire_id) do
|
|
145
145
|
params = compact_query_params(sort: sort, direction: direction)
|
|
146
|
-
paginate("/chats/#{campfire_id}/uploads.json", params: params)
|
|
146
|
+
paginate("/chats/#{campfire_id}/uploads.json", params: params, operation: "ListCampfireUploads")
|
|
147
147
|
end
|
|
148
148
|
end
|
|
149
149
|
|
|
@@ -43,7 +43,7 @@ module Basecamp
|
|
|
43
43
|
# @return [Hash] response data
|
|
44
44
|
def get(column_id:)
|
|
45
45
|
with_operation(service: "cardcolumns", operation: "get", is_mutation: false, resource_id: column_id) do
|
|
46
|
-
http_get("/card_tables/columns/#{column_id}").json
|
|
46
|
+
http_get("/card_tables/columns/#{column_id}", operation: "GetCardColumn").json
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -36,7 +36,7 @@ module Basecamp
|
|
|
36
36
|
# @return [Hash] response data
|
|
37
37
|
def get(step_id:)
|
|
38
38
|
with_operation(service: "cardsteps", operation: "get", is_mutation: false, resource_id: step_id) do
|
|
39
|
-
http_get("/card_tables/steps/#{step_id}").json
|
|
39
|
+
http_get("/card_tables/steps/#{step_id}", operation: "GetCardStep").json
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Hash] response data
|
|
13
13
|
def get(card_table_id:)
|
|
14
14
|
with_operation(service: "cardtables", operation: "get", is_mutation: false, resource_id: card_table_id) do
|
|
15
|
-
http_get("/card_tables/#{card_table_id}").json
|
|
15
|
+
http_get("/card_tables/#{card_table_id}", operation: "GetCardTable").json
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Hash] response data
|
|
13
13
|
def get(card_id:)
|
|
14
14
|
with_operation(service: "cards", operation: "get", is_mutation: false, resource_id: card_id) do
|
|
15
|
-
http_get("/card_tables/cards/#{card_id}").json
|
|
15
|
+
http_get("/card_tables/cards/#{card_id}", operation: "GetCard").json
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -46,7 +46,7 @@ module Basecamp
|
|
|
46
46
|
# @return [Enumerator<Hash>] paginated results
|
|
47
47
|
def list(column_id:)
|
|
48
48
|
wrap_paginated(service: "cards", operation: "list", is_mutation: false, resource_id: column_id) do
|
|
49
|
-
paginate("/card_tables/lists/#{column_id}/cards.json")
|
|
49
|
+
paginate("/card_tables/lists/#{column_id}/cards.json", operation: "ListCards")
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -11,7 +11,7 @@ module Basecamp
|
|
|
11
11
|
# @return [Enumerator<Hash>] paginated results
|
|
12
12
|
def reminders()
|
|
13
13
|
wrap_paginated(service: "checkins", operation: "reminders", is_mutation: false) do
|
|
14
|
-
paginate("/my/question_reminders.json")
|
|
14
|
+
paginate("/my/question_reminders.json", operation: "GetQuestionReminders")
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ module Basecamp
|
|
|
20
20
|
# @return [Hash] response data
|
|
21
21
|
def get_answer(answer_id:)
|
|
22
22
|
with_operation(service: "checkins", operation: "get_answer", is_mutation: false, resource_id: answer_id) do
|
|
23
|
-
http_get("/question_answers/#{answer_id}").json
|
|
23
|
+
http_get("/question_answers/#{answer_id}", operation: "GetAnswer").json
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -41,7 +41,7 @@ module Basecamp
|
|
|
41
41
|
# @return [Hash] response data
|
|
42
42
|
def get_questionnaire(questionnaire_id:)
|
|
43
43
|
with_operation(service: "checkins", operation: "get_questionnaire", is_mutation: false, resource_id: questionnaire_id) do
|
|
44
|
-
http_get("/questionnaires/#{questionnaire_id}").json
|
|
44
|
+
http_get("/questionnaires/#{questionnaire_id}", operation: "GetQuestionnaire").json
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -50,7 +50,7 @@ module Basecamp
|
|
|
50
50
|
# @return [Enumerator<Hash>] paginated results
|
|
51
51
|
def list_questions(questionnaire_id:)
|
|
52
52
|
wrap_paginated(service: "checkins", operation: "list_questions", is_mutation: false, resource_id: questionnaire_id) do
|
|
53
|
-
paginate("/questionnaires/#{questionnaire_id}/questions.json")
|
|
53
|
+
paginate("/questionnaires/#{questionnaire_id}/questions.json", operation: "ListQuestions")
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -71,7 +71,7 @@ module Basecamp
|
|
|
71
71
|
# @return [Hash] response data
|
|
72
72
|
def get_question(question_id:)
|
|
73
73
|
with_operation(service: "checkins", operation: "get_question", is_mutation: false, resource_id: question_id) do
|
|
74
|
-
http_get("/questions/#{question_id}").json
|
|
74
|
+
http_get("/questions/#{question_id}", operation: "GetQuestion").json
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -92,7 +92,7 @@ module Basecamp
|
|
|
92
92
|
# @return [Enumerator<Hash>] paginated results
|
|
93
93
|
def list_answers(question_id:)
|
|
94
94
|
wrap_paginated(service: "checkins", operation: "list_answers", is_mutation: false, resource_id: question_id) do
|
|
95
|
-
paginate("/questions/#{question_id}/answers.json")
|
|
95
|
+
paginate("/questions/#{question_id}/answers.json", operation: "ListAnswers")
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -112,7 +112,7 @@ module Basecamp
|
|
|
112
112
|
# @return [Enumerator<Hash>] paginated results
|
|
113
113
|
def answerers(question_id:)
|
|
114
114
|
wrap_paginated(service: "checkins", operation: "answerers", is_mutation: false, resource_id: question_id) do
|
|
115
|
-
paginate("/questions/#{question_id}/answers/by.json")
|
|
115
|
+
paginate("/questions/#{question_id}/answers/by.json", operation: "ListQuestionAnswerers")
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -122,7 +122,7 @@ module Basecamp
|
|
|
122
122
|
# @return [Enumerator<Hash>] paginated results
|
|
123
123
|
def by_person(question_id:, person_id:)
|
|
124
124
|
wrap_paginated(service: "checkins", operation: "by_person", is_mutation: false, resource_id: person_id) do
|
|
125
|
-
paginate("/questions/#{question_id}/answers/by/#{person_id}")
|
|
125
|
+
paginate("/questions/#{question_id}/answers/by/#{person_id}", operation: "GetAnswersByPerson")
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
|
|
@@ -14,7 +14,7 @@ module Basecamp
|
|
|
14
14
|
def list(sort: nil, direction: nil)
|
|
15
15
|
wrap_paginated(service: "clientapprovals", operation: "list", is_mutation: false) do
|
|
16
16
|
params = compact_query_params(sort: sort, direction: direction)
|
|
17
|
-
paginate("/client/approvals.json", params: params)
|
|
17
|
+
paginate("/client/approvals.json", params: params, operation: "ListClientApprovals")
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ module Basecamp
|
|
|
23
23
|
# @return [Hash] response data
|
|
24
24
|
def get(approval_id:)
|
|
25
25
|
with_operation(service: "clientapprovals", operation: "get", is_mutation: false, resource_id: approval_id) do
|
|
26
|
-
http_get("/client/approvals/#{approval_id}").json
|
|
26
|
+
http_get("/client/approvals/#{approval_id}", operation: "GetClientApproval").json
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -14,7 +14,7 @@ module Basecamp
|
|
|
14
14
|
def list(sort: nil, direction: nil)
|
|
15
15
|
wrap_paginated(service: "clientcorrespondences", operation: "list", is_mutation: false) do
|
|
16
16
|
params = compact_query_params(sort: sort, direction: direction)
|
|
17
|
-
paginate("/client/correspondences.json", params: params)
|
|
17
|
+
paginate("/client/correspondences.json", params: params, operation: "ListClientCorrespondences")
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ module Basecamp
|
|
|
23
23
|
# @return [Hash] response data
|
|
24
24
|
def get(correspondence_id:)
|
|
25
25
|
with_operation(service: "clientcorrespondences", operation: "get", is_mutation: false, resource_id: correspondence_id) do
|
|
26
|
-
http_get("/client/correspondences/#{correspondence_id}").json
|
|
26
|
+
http_get("/client/correspondences/#{correspondence_id}", operation: "GetClientCorrespondence").json
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Enumerator<Hash>] paginated results
|
|
13
13
|
def list(recording_id:)
|
|
14
14
|
wrap_paginated(service: "clientreplies", operation: "list", is_mutation: false, resource_id: recording_id) do
|
|
15
|
-
paginate("/client/recordings/#{recording_id}/replies.json")
|
|
15
|
+
paginate("/client/recordings/#{recording_id}/replies.json", operation: "ListClientReplies")
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -22,7 +22,7 @@ module Basecamp
|
|
|
22
22
|
# @return [Hash] response data
|
|
23
23
|
def get(recording_id:, reply_id:)
|
|
24
24
|
with_operation(service: "clientreplies", operation: "get", is_mutation: false, resource_id: reply_id) do
|
|
25
|
-
http_get("/client/recordings/#{recording_id}/replies/#{reply_id}").json
|
|
25
|
+
http_get("/client/recordings/#{recording_id}/replies/#{reply_id}", operation: "GetClientReply").json
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Hash] response data
|
|
13
13
|
def get(comment_id:)
|
|
14
14
|
with_operation(service: "comments", operation: "get", is_mutation: false, resource_id: comment_id) do
|
|
15
|
-
http_get("/comments/#{comment_id}").json
|
|
15
|
+
http_get("/comments/#{comment_id}", operation: "GetComment").json
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -31,7 +31,7 @@ module Basecamp
|
|
|
31
31
|
# @return [Enumerator<Hash>] paginated results
|
|
32
32
|
def list(recording_id:)
|
|
33
33
|
wrap_paginated(service: "comments", operation: "list", is_mutation: false, resource_id: recording_id) do
|
|
34
|
-
paginate("/recordings/#{recording_id}/comments.json")
|
|
34
|
+
paginate("/recordings/#{recording_id}/comments.json", operation: "ListComments")
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Hash] response data
|
|
13
13
|
def get(document_id:)
|
|
14
14
|
with_operation(service: "documents", operation: "get", is_mutation: false, resource_id: document_id) do
|
|
15
|
-
http_get("/documents/#{document_id}").json
|
|
15
|
+
http_get("/documents/#{document_id}", operation: "GetDocument").json
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -32,7 +32,7 @@ module Basecamp
|
|
|
32
32
|
# @return [Enumerator<Hash>] paginated results
|
|
33
33
|
def list(vault_id:)
|
|
34
34
|
wrap_paginated(service: "documents", operation: "list", is_mutation: false, resource_id: vault_id) do
|
|
35
|
-
paginate("/vaults/#{vault_id}/documents.json")
|
|
35
|
+
paginate("/vaults/#{vault_id}/documents.json", operation: "ListDocuments")
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -12,7 +12,7 @@ module Basecamp
|
|
|
12
12
|
# @return [Enumerator<Hash>] paginated results
|
|
13
13
|
def list(recording_id:)
|
|
14
14
|
wrap_paginated(service: "events", operation: "list", is_mutation: false, resource_id: recording_id) do
|
|
15
|
-
paginate("/recordings/#{recording_id}/events.json")
|
|
15
|
+
paginate("/recordings/#{recording_id}/events.json", operation: "ListEvents")
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|