basecamp-sdk 0.11.0 → 0.13.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +127 -8
  3. data/lib/basecamp/client.rb +55 -11
  4. data/lib/basecamp/config.rb +69 -0
  5. data/lib/basecamp/generated/metadata.json +443 -133
  6. data/lib/basecamp/generated/services/base_service.rb +37 -16
  7. data/lib/basecamp/generated/services/bookmarks_service.rb +50 -0
  8. data/lib/basecamp/generated/services/boosts_service.rb +12 -6
  9. data/lib/basecamp/generated/services/calendars_service.rb +30 -0
  10. data/lib/basecamp/generated/services/campfires_service.rb +54 -41
  11. data/lib/basecamp/generated/services/cards_service.rb +6 -3
  12. data/lib/basecamp/generated/services/checkins_service.rb +28 -15
  13. data/lib/basecamp/generated/services/client_approvals_service.rb +8 -5
  14. data/lib/basecamp/generated/services/client_correspondences_service.rb +8 -5
  15. data/lib/basecamp/generated/services/client_replies_service.rb +12 -7
  16. data/lib/basecamp/generated/services/cloud_files_service.rb +57 -0
  17. data/lib/basecamp/generated/services/comments_service.rb +6 -3
  18. data/lib/basecamp/generated/services/documents_service.rb +9 -6
  19. data/lib/basecamp/generated/services/drafts_service.rb +22 -0
  20. data/lib/basecamp/generated/services/events_service.rb +6 -3
  21. data/lib/basecamp/generated/services/everything_service.rb +116 -69
  22. data/lib/basecamp/generated/services/folders_service.rb +62 -0
  23. data/lib/basecamp/generated/services/forwards_service.rb +12 -17
  24. data/lib/basecamp/generated/services/gauges_service.rb +12 -7
  25. data/lib/basecamp/generated/services/google_documents_service.rb +61 -0
  26. data/lib/basecamp/generated/services/message_types_service.rb +4 -3
  27. data/lib/basecamp/generated/services/messages_service.rb +6 -4
  28. data/lib/basecamp/generated/services/my_assignments_service.rb +31 -0
  29. data/lib/basecamp/generated/services/my_notes_service.rb +28 -0
  30. data/lib/basecamp/generated/services/my_notifications_service.rb +8 -5
  31. data/lib/basecamp/generated/services/people_service.rb +17 -10
  32. data/lib/basecamp/generated/services/projects_service.rb +26 -4
  33. data/lib/basecamp/generated/services/recordings_service.rb +6 -13
  34. data/lib/basecamp/generated/services/reports_service.rb +15 -9
  35. data/lib/basecamp/generated/services/schedules_service.rb +90 -16
  36. data/lib/basecamp/generated/services/search_service.rb +6 -4
  37. data/lib/basecamp/generated/services/templates_service.rb +6 -4
  38. data/lib/basecamp/generated/services/timeline_service.rb +6 -3
  39. data/lib/basecamp/generated/services/timesheets_service.rb +22 -8
  40. data/lib/basecamp/generated/services/todolist_groups_service.rb +7 -4
  41. data/lib/basecamp/generated/services/todolists_service.rb +11 -9
  42. data/lib/basecamp/generated/services/todos_service.rb +23 -14
  43. data/lib/basecamp/generated/services/uploads_service.rb +10 -6
  44. data/lib/basecamp/generated/services/vaults_service.rb +6 -3
  45. data/lib/basecamp/generated/services/webhooks_service.rb +4 -3
  46. data/lib/basecamp/generated/types.rb +847 -147
  47. data/lib/basecamp/http.rb +346 -163
  48. data/lib/basecamp/list_enumerator.rb +29 -0
  49. data/lib/basecamp/list_meta.rb +44 -0
  50. data/lib/basecamp/services/cards_extensions.rb +35 -27
  51. data/lib/basecamp/services/documents_extensions.rb +136 -0
  52. data/lib/basecamp/services/merge_safe.rb +255 -0
  53. data/lib/basecamp/services/schedules_extensions.rb +354 -0
  54. data/lib/basecamp/services/todolists_extensions.rb +274 -0
  55. data/lib/basecamp/services/todos_extensions.rb +22 -6
  56. data/lib/basecamp/validation_error.rb +11 -1
  57. data/lib/basecamp/version.rb +2 -2
  58. data/lib/basecamp.rb +94 -4
  59. data/scripts/generate-services.rb +74 -25
  60. data/scripts/generate-types.rb +2 -1
  61. data/scripts/go_type_spellings.rb +26 -0
  62. metadata +16 -2
@@ -32,6 +32,37 @@ module Basecamp
32
32
  http_get("/my/assignments/due.json", params: compact_query_params(scope: scope), operation: "GetMyDueAssignments").json
33
33
  end
34
34
  end
35
+
36
+ # Add a recording to Up Next — the current user's ordered list of prioritized
37
+ # @param id [Integer] The recording id to prioritize.
38
+ # @return [void]
39
+ def prioritize_assignment(id:)
40
+ with_operation(service: "myassignments", operation: "prioritize_assignment", is_mutation: true) do
41
+ http_post("/my/priorities.json", body: compact_params(id: id))
42
+ nil
43
+ end
44
+ end
45
+
46
+ # Remove a recording from Up Next (returns 204 No Content). Exact-target:
47
+ # @param recording_id [Integer] recording id ID
48
+ # @return [void]
49
+ def deprioritize_assignment(recording_id:)
50
+ with_operation(service: "myassignments", operation: "deprioritize_assignment", is_mutation: true, resource_id: recording_id) do
51
+ http_delete("/my/priorities/#{recording_id}")
52
+ nil
53
+ end
54
+ end
55
+
56
+ # Move an already-prioritized recording to a new 1-based position in Up Next
57
+ # @param source_id [Integer] The recording id to move, chosen the same way as when prioritizing.
58
+ # @param position [Integer] The 1-based position to move it to.
59
+ # @return [void]
60
+ def reorder_up_next(source_id:, position:)
61
+ with_operation(service: "myassignments", operation: "reorder_up_next", is_mutation: true) do
62
+ http_post("/my/priority_moves.json", body: compact_params(source_id: source_id, position: position))
63
+ nil
64
+ end
65
+ end
35
66
  end
36
67
  end
37
68
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basecamp
4
+ module Services
5
+ # Service for MyNotes operations
6
+ #
7
+ # @generated from OpenAPI spec
8
+ class MyNotesService < BaseService
9
+
10
+ # Get the authenticated user's note — a per-person notebook singleton at
11
+ # @return [Hash] response data
12
+ def get_my_note()
13
+ with_operation(service: "mynotes", operation: "get_my_note", is_mutation: false) do
14
+ http_get("/my/notes.json", operation: "GetMyNote").json
15
+ end
16
+ end
17
+
18
+ # Replace the note's content, recording a new revision server-side.
19
+ # @param note [Hash] note
20
+ # @return [Hash] response data
21
+ def update_my_note(note:)
22
+ with_operation(service: "mynotes", operation: "update_my_note", is_mutation: true) do
23
+ http_put("/my/notes.json", body: compact_params(note: note)).json
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -8,7 +8,9 @@ module Basecamp
8
8
  class MyNotificationsService < BaseService
9
9
 
10
10
  # Get the current user's notification inbox (the "Hey!" menu).
11
- # @param page [Integer, nil] Page number for paginating through read items. Defaults to 1.
11
+ # @param page [Integer, nil] Page number for paginating through read items. Defaults to 1. This
12
+ # operation is not auto-paginated in any SDK, so a page is returned as
13
+ # asked for and later pages are not followed.
12
14
  # @param limit_bubble_ups [Boolean, nil] Set to true to cap `bubble_ups` at 2 current bubble-ups and omit the
13
15
  # `scheduled_bubble_ups` key entirely. Defaults to false. Use the dedicated
14
16
  # bubble-ups endpoint (GetBubbleUps) to page through all current and
@@ -21,12 +23,13 @@ module Basecamp
21
23
  end
22
24
 
23
25
  # Get the current user's current and scheduled bubble-ups (paginated, 50 per page).
24
- # @param page [Integer, nil] Page number. Defaults to 1.
25
- # @return [Enumerator<Hash>] paginated results
26
- def get_bubble_ups(page: nil)
26
+ # @param page [Integer, nil] Page number. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
27
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
28
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
29
+ def get_bubble_ups(page: nil, max_items: nil)
27
30
  wrap_paginated(service: "mynotifications", operation: "get_bubble_ups", is_mutation: false) do
28
31
  params = compact_query_params(page: page)
29
- paginate("/my/readings/bubble_ups.json", params: params, operation: "GetBubbleUps")
32
+ paginate("/my/readings/bubble_ups.json", params: params, operation: "GetBubbleUps", max_items: max_items)
30
33
  end
31
34
  end
32
35
 
@@ -8,10 +8,11 @@ module Basecamp
8
8
  class PeopleService < BaseService
9
9
 
10
10
  # List all account users who can be pinged
11
- # @return [Enumerator<Hash>] paginated results
12
- def list_pingable()
11
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
12
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
13
+ def list_pingable(max_items: nil)
13
14
  wrap_paginated(service: "people", operation: "list_pingable", is_mutation: false) do
14
- paginate("/circles/people.json", operation: "ListPingablePeople")
15
+ paginate("/circles/people.json", operation: "ListPingablePeople", max_items: max_items)
15
16
  end
16
17
  end
17
18
 
@@ -23,7 +24,7 @@ module Basecamp
23
24
  end
24
25
  end
25
26
 
26
- # Update the current user's preferences
27
+ # Update the current user's preferences.
27
28
  # @param person [Hash] person
28
29
  # @return [Hash] response data
29
30
  def update_my_preferences(person:)
@@ -58,10 +59,13 @@ module Basecamp
58
59
  end
59
60
 
60
61
  # List all people visible to the current user
61
- # @return [Enumerator<Hash>] paginated results
62
- def list()
62
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
63
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
64
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
65
+ def list(page: nil, max_items: nil)
63
66
  wrap_paginated(service: "people", operation: "list", is_mutation: false) do
64
- paginate("/people.json", operation: "ListPeople")
67
+ params = compact_query_params(page: page)
68
+ paginate("/people.json", params: params, operation: "ListPeople", max_items: max_items)
65
69
  end
66
70
  end
67
71
 
@@ -105,10 +109,13 @@ module Basecamp
105
109
 
106
110
  # List all active people on a project
107
111
  # @param project_id [Integer] project id ID
108
- # @return [Enumerator<Hash>] paginated results
109
- def list_for_project(project_id:)
112
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
113
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
114
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
115
+ def list_for_project(project_id:, page: nil, max_items: nil)
110
116
  wrap_paginated(service: "people", operation: "list_for_project", is_mutation: false, project_id: project_id) do
111
- paginate("/projects/#{project_id}/people.json", operation: "ListProjectPeople")
117
+ params = compact_query_params(page: page)
118
+ paginate("/projects/#{project_id}/people.json", params: params, operation: "ListProjectPeople", max_items: max_items)
112
119
  end
113
120
  end
114
121
 
@@ -9,11 +9,13 @@ module Basecamp
9
9
 
10
10
  # List projects (active by default; optionally archived/trashed)
11
11
  # @param status [String, nil] active|archived|trashed
12
- # @return [Enumerator<Hash>] paginated results
13
- def list(status: nil)
12
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
13
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
14
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
15
+ def list(status: nil, page: nil, max_items: nil)
14
16
  wrap_paginated(service: "projects", operation: "list", is_mutation: false) do
15
- params = compact_query_params(status: status)
16
- paginate("/projects.json", params: params, operation: "ListProjects")
17
+ params = compact_query_params(status: status, page: page)
18
+ paginate("/projects.json", params: params, operation: "ListProjects", max_items: max_items)
17
19
  end
18
20
  end
19
21
 
@@ -58,6 +60,26 @@ module Basecamp
58
60
  nil
59
61
  end
60
62
  end
63
+
64
+ # Restore a project to active status from trash as well as from the archive (returns 204 No Content).
65
+ # @param project_id [Integer] project id ID
66
+ # @return [void]
67
+ def unarchive(project_id:)
68
+ with_operation(service: "projects", operation: "unarchive", is_mutation: true, project_id: project_id) do
69
+ http_put("/projects/#{project_id}/status/active.json")
70
+ nil
71
+ end
72
+ end
73
+
74
+ # Archive a project, removing it from the active project list (returns 204 No Content).
75
+ # @param project_id [Integer] project id ID
76
+ # @return [void]
77
+ def archive(project_id:)
78
+ with_operation(service: "projects", operation: "archive", is_mutation: true, project_id: project_id) do
79
+ http_put("/projects/#{project_id}/status/archived.json")
80
+ nil
81
+ end
82
+ end
61
83
  end
62
84
  end
63
85
  end
@@ -13,20 +13,13 @@ module Basecamp
13
13
  # @param status [String, nil] active|archived|trashed
14
14
  # @param sort [String, nil] created_at|updated_at
15
15
  # @param direction [String, nil] asc|desc
16
- # @return [Enumerator<Hash>] paginated results
17
- def list(type:, bucket: nil, status: nil, sort: nil, direction: nil)
16
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
17
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
18
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
19
+ def list(type:, bucket: nil, status: nil, sort: nil, direction: nil, page: nil, max_items: nil)
18
20
  wrap_paginated(service: "recordings", operation: "list", is_mutation: false) do
19
- params = compact_query_params(type: type, bucket: bucket, status: status, sort: sort, direction: direction)
20
- paginate("/projects/recordings.json", params: params, operation: "ListRecordings")
21
- end
22
- end
23
-
24
- # Get a single recording by id
25
- # @param recording_id [Integer] recording id ID
26
- # @return [Hash] response data
27
- def get(recording_id:)
28
- with_operation(service: "recordings", operation: "get", is_mutation: false, resource_id: recording_id) do
29
- http_get("/recordings/#{recording_id}", operation: "GetRecording").json
21
+ params = compact_query_params(type: type, bucket: bucket, status: status, sort: sort, direction: direction, page: page)
22
+ paginate("/projects/recordings.json", params: params, operation: "ListRecordings", max_items: max_items)
30
23
  end
31
24
  end
32
25
 
@@ -8,18 +8,21 @@ module Basecamp
8
8
  class ReportsService < BaseService
9
9
 
10
10
  # Get account-wide activity feed (progress report)
11
- # @return [Enumerator<Hash>] paginated results
12
- def progress()
11
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
12
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
13
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
14
+ def progress(page: nil, max_items: nil)
13
15
  wrap_paginated(service: "reports", operation: "progress", is_mutation: false) do
14
- paginate("/reports/progress.json", operation: "GetProgressReport")
16
+ params = compact_query_params(page: page)
17
+ paginate("/reports/progress.json", params: params, operation: "GetProgressReport", max_items: max_items)
15
18
  end
16
19
  end
17
20
 
18
21
  # Get upcoming schedule entries and assignable items within a date window.
19
- # @param window_starts_on [String, nil] window starts on
20
- # @param window_ends_on [String, nil] window ends on
22
+ # @param window_starts_on [String] Inclusive first day of the window, `YYYY-MM-DD`. Required — BC3 answers 400 without it.
23
+ # @param window_ends_on [String] Inclusive last day of the window, `YYYY-MM-DD`. Required — BC3 answers 400 without it.
21
24
  # @return [Hash] response data
22
- def upcoming(window_starts_on: nil, window_ends_on: nil)
25
+ def upcoming(window_starts_on:, window_ends_on:)
23
26
  with_operation(service: "reports", operation: "upcoming", is_mutation: false) do
24
27
  http_get("/reports/schedules/upcoming.json", params: compact_query_params(window_starts_on: window_starts_on, window_ends_on: window_ends_on), operation: "GetUpcomingSchedule").json
25
28
  end
@@ -45,10 +48,13 @@ module Basecamp
45
48
 
46
49
  # Get a person's activity timeline
47
50
  # @param person_id [Integer] person id ID
48
- # @return [Hash] response data
49
- def person_progress(person_id:)
51
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
52
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
53
+ # @return [Hash] wrapper fields merged with a ListEnumerator of the paginated items
54
+ def person_progress(person_id:, page: nil, max_items: nil)
50
55
  wrap_paginated_wrapped(key: "events", service: "reports", operation: "person_progress", is_mutation: false, resource_id: person_id) do
51
- paginate_wrapped("/reports/users/progress/#{person_id}.json", key: "events", operation: "GetPersonProgress")
56
+ params = compact_query_params(page: page)
57
+ paginate_wrapped("/reports/users/progress/#{person_id}.json", key: "events", params: params, operation: "GetPersonProgress", max_items: max_items)
52
58
  end
53
59
  end
54
60
  end
@@ -16,14 +16,16 @@ module Basecamp
16
16
  end
17
17
  end
18
18
 
19
- # Update an existing schedule entry
19
+ # Replace a schedule entry with a new complete representation.
20
20
  # @param entry_id [Integer] entry id ID
21
21
  # @param summary [String, nil] summary
22
- # @param starts_at [String, nil] starts at (RFC3339 (e.g., 2024-12-15T09:00:00Z))
23
- # @param ends_at [String, nil] ends at (RFC3339 (e.g., 2024-12-15T09:00:00Z))
22
+ # @param starts_at [String] The entry's start, as a bare date ("2026-06-01") for an all-day entry or a
23
+ # full timestamp otherwise. Same rule as CreateScheduleEntry: send it
24
+ # verbatim, never parsed and re-rendered.
25
+ # @param ends_at [String] The entry's end. See starts_at for the date-vs-timestamp rule.
24
26
  # @param description [String, nil] description
25
27
  # @param participant_ids [Array, nil] Replaces the entry's participants.
26
- #
28
+ #
27
29
  # Omitting this member preserves the current participants; sending an empty
28
30
  # array clears them. That guarantee is BC3-side and recent: until
29
31
  # basecamp/bc3#12425, `Schedules::EntriesController#update` called
@@ -31,12 +33,36 @@ module Basecamp
31
33
  # including the shape in BC3's own "Update a schedule entry" doc example —
32
34
  # silently removed every participant and notified each one. The controller
33
35
  # now guards on the request actually addressing participants.
34
- # @param all_day [Boolean, nil] all day
36
+ # @param all_day [Boolean, nil] Whether the entry occupies whole days rather than a time range.
37
+ #
38
+ # Not carved out, and the carve-out list is what makes that dangerous to
39
+ # forget: `schedule_entries.all_day` is NOT NULL with a `false` default, so
40
+ # omitting this member on a replace resets it — silently converting an
41
+ # all-day entry into a midnight-to-midnight timed one. The SDK's merge-safe
42
+ # update and edit resend it from the read-back for exactly this reason.
43
+ #
44
+ # Sending an explicit null is worse than omitting it: the column rejects
45
+ # NULL, so BC3 raises rather than falling back to the default. The same is
46
+ # true of highlighted.
35
47
  # @param notify [Boolean, nil] notify
48
+ # @param url [String, nil] The entry's join link — a video-call URL or similar, up to 2500
49
+ # characters, validated as a URL when present.
50
+ #
51
+ # Omitting this member preserves the current join link; sending an empty
52
+ # string clears it. Read it back as `join_url`, never as `url`: the entry's
53
+ # `url` is its own Basecamp API URL, written by a partial that renders
54
+ # before this one, so BC3 emits the join link under a non-colliding key.
55
+ # Echoing the response's `url` into this member would write the API URL into
56
+ # the join link.
57
+ # @param highlighted [Boolean, nil] Whether the entry is highlighted on the schedule.
58
+ #
59
+ # Omitting this member preserves the current highlight; sending false
60
+ # removes it. Preserved on omission because until basecamp/bc3#12502 the
61
+ # field was writable but never returned, so no caller could resend it.
36
62
  # @return [Hash] response data
37
- def update_entry(entry_id:, summary: nil, starts_at: nil, ends_at: nil, description: nil, participant_ids: nil, all_day: nil, notify: nil)
38
- with_operation(service: "schedules", operation: "update_entry", is_mutation: true, resource_id: entry_id) do
39
- http_put("/schedule_entries/#{entry_id}", body: compact_params(summary: summary, starts_at: starts_at, ends_at: ends_at, description: description, participant_ids: participant_ids, all_day: all_day, notify: notify)).json
63
+ def replace_entry(entry_id:, starts_at:, ends_at:, summary: nil, description: nil, participant_ids: nil, all_day: nil, notify: nil, url: nil, highlighted: nil)
64
+ with_operation(service: "schedules", operation: "replace_entry", is_mutation: true, resource_id: entry_id) do
65
+ http_put("/schedule_entries/#{entry_id}", body: compact_params(summary: summary, starts_at: starts_at, ends_at: ends_at, description: description, participant_ids: participant_ids, all_day: all_day, notify: notify, url: url, highlighted: highlighted)).json
40
66
  end
41
67
  end
42
68
 
@@ -72,29 +98,77 @@ module Basecamp
72
98
  # List entries on a schedule
73
99
  # @param schedule_id [Integer] schedule id ID
74
100
  # @param status [String, nil] active|archived|trashed
75
- # @return [Enumerator<Hash>] paginated results
76
- def list_entries(schedule_id:, status: nil)
101
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
102
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
103
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
104
+ def list_entries(schedule_id:, status: nil, page: nil, max_items: nil)
77
105
  wrap_paginated(service: "schedules", operation: "list_entries", is_mutation: false, resource_id: schedule_id) do
78
- params = compact_query_params(status: status)
79
- paginate("/schedules/#{schedule_id}/entries.json", params: params, operation: "ListScheduleEntries")
106
+ params = compact_query_params(status: status, page: page)
107
+ paginate("/schedules/#{schedule_id}/entries.json", params: params, operation: "ListScheduleEntries", max_items: max_items)
80
108
  end
81
109
  end
82
110
 
83
111
  # Create a new schedule entry
84
112
  # @param schedule_id [Integer] schedule id ID
85
113
  # @param summary [String] summary
86
- # @param starts_at [String] starts at (RFC3339 (e.g., 2024-12-15T09:00:00Z))
87
- # @param ends_at [String] ends at (RFC3339 (e.g., 2024-12-15T09:00:00Z))
114
+ # @param starts_at [String] The entry's start, as a bare date ("2026-06-01") for an all-day entry or a
115
+ # full timestamp ("2026-06-01T09:00:00Z") otherwise — the same two forms the
116
+ # response renders, and the same two ReplaceScheduleEntry accepts.
117
+ #
118
+ # Create and replace share one permit list:
119
+ # `Schedules::Entries::BaseController#base_schedule_entry_params` is what
120
+ # both `new_schedule_entry_params` and `update_schedule_entry_params` call,
121
+ # and Schedule::Entry does no format-specific parsing of either bound, so
122
+ # whatever one operation takes the other takes too.
123
+ #
124
+ # Treat the value as opaque and send it verbatim. Parsing it into a
125
+ # date-time type and re-rendering rewrites an all-day entry's bounds into
126
+ # midnight timestamps, which is why every SDK models it as a string.
127
+ # @param ends_at [String] The entry's end. See starts_at for the date-vs-timestamp rule.
88
128
  # @param description [String, nil] description
89
129
  # @param participant_ids [Array, nil] participant ids
90
130
  # @param all_day [Boolean, nil] all day
91
131
  # @param notify [Boolean, nil] notify
132
+ # @param url [String, nil] The entry's join link — a video-call URL or similar, up to 2500
133
+ # characters, validated as a URL when present. A scheme-less value is
134
+ # normalized to `https://`.
135
+ #
136
+ # Spell it `url` on the way in and read it back as `join_url`: the response
137
+ # key `url` is the entry's own Basecamp API URL, written by a partial that
138
+ # renders before this field, so BC3 emits the join link under a
139
+ # non-colliding name. Sending `join_url` instead is silently dropped by
140
+ # strong parameters — the create succeeds with no join link.
141
+ #
142
+ # Accepted on create since long before it was documented:
143
+ # `Schedules::Entries::BaseController#base_schedule_entry_params` permits it
144
+ # and `new_schedule_entry_params` passes it through unchanged for API
145
+ # requests. Modeling it only on ReplaceScheduleEntry forced callers into a
146
+ # three-request read-modify-write for a field the create already took — and
147
+ # create is the notifying write, so participants learned about a video call
148
+ # before its link existed.
149
+ # @param highlighted [Boolean, nil] Whether the entry is highlighted on the schedule. Defaults to false.
150
+ #
151
+ # Do not send an explicit null: `schedule_entries.highlighted` is NOT NULL,
152
+ # so BC3 raises rather than falling back to the default. Omit it instead —
153
+ # every SDK's request compactor already drops unset members.
154
+ # @param status [String, nil] Publication state at creation — `active|drafted`, defaulting to `active`
155
+ # for an API create.
156
+ #
157
+ # A top-level parameter, not part of the entry's attributes: `status` is a
158
+ # Recording column, so `wrap_parameters` leaves it outside the
159
+ # `schedule_entry` envelope and `Recording::StatusParam#status_param` reads
160
+ # it directly. On create it accepts `drafted`, `active`, `archived` or
161
+ # `trashed` and raises `ActionController::BadRequest` — a 400, not a 422 —
162
+ # for anything else; the two documented values are the two worth sending.
163
+ #
164
+ # Unlike messages and documents, schedule-entry drafts are not listed by
165
+ # GetMyDrafts.
92
166
  # @param subscriptions [Array, nil] subscriptions
93
167
  # @param visible_to_clients [Boolean, nil] visible to clients
94
168
  # @return [Hash] response data
95
- def create_entry(schedule_id:, summary:, starts_at:, ends_at:, description: nil, participant_ids: nil, all_day: nil, notify: nil, subscriptions: nil, visible_to_clients: nil)
169
+ def create_entry(schedule_id:, summary:, starts_at:, ends_at:, description: nil, participant_ids: nil, all_day: nil, notify: nil, url: nil, highlighted: nil, status: nil, subscriptions: nil, visible_to_clients: nil)
96
170
  with_operation(service: "schedules", operation: "create_entry", is_mutation: true, resource_id: schedule_id) do
97
- http_post("/schedules/#{schedule_id}/entries.json", body: compact_params(summary: summary, starts_at: starts_at, ends_at: ends_at, description: description, participant_ids: participant_ids, all_day: all_day, notify: notify, subscriptions: subscriptions, visible_to_clients: visible_to_clients)).json
171
+ http_post("/schedules/#{schedule_id}/entries.json", body: compact_params(summary: summary, starts_at: starts_at, ends_at: ends_at, description: description, participant_ids: participant_ids, all_day: all_day, notify: notify, url: url, highlighted: highlighted, status: status, subscriptions: subscriptions, visible_to_clients: visible_to_clients)).json
98
172
  end
99
173
  end
100
174
  end
@@ -21,11 +21,13 @@ module Basecamp
21
21
  # @param type [String, nil] Deprecated: prefer type_names[].
22
22
  # @param bucket_id [Integer, nil] Deprecated: prefer bucket_ids[].
23
23
  # @param creator_id [Integer, nil] Deprecated: prefer creator_ids[].
24
- # @return [Enumerator<Hash>] paginated results
25
- def search(q:, type_names: nil, bucket_ids: nil, creator_ids: nil, file_type: nil, exclude_chat: nil, since: nil, sort: nil, type: nil, bucket_id: nil, creator_id: nil)
24
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
25
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
26
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
27
+ def search(q:, type_names: nil, bucket_ids: nil, creator_ids: nil, file_type: nil, exclude_chat: nil, since: nil, sort: nil, type: nil, bucket_id: nil, creator_id: nil, page: nil, max_items: nil)
26
28
  wrap_paginated(service: "search", operation: "search", is_mutation: false) do
27
- params = compact_query_params(q: q, type_names: type_names, bucket_ids: bucket_ids, creator_ids: creator_ids, file_type: file_type, exclude_chat: exclude_chat, since: since, sort: sort, type: type, bucket_id: bucket_id, creator_id: creator_id)
28
- paginate("/search.json", params: params, operation: "Search")
29
+ params = compact_query_params(q: q, type_names: type_names, bucket_ids: bucket_ids, creator_ids: creator_ids, file_type: file_type, exclude_chat: exclude_chat, since: since, sort: sort, type: type, bucket_id: bucket_id, creator_id: creator_id, page: page)
30
+ paginate("/search.json", params: params, operation: "Search", max_items: max_items)
29
31
  end
30
32
  end
31
33
 
@@ -9,11 +9,13 @@ module Basecamp
9
9
 
10
10
  # List all templates visible to the current user
11
11
  # @param status [String, nil] active|archived|trashed
12
- # @return [Enumerator<Hash>] paginated results
13
- def list(status: nil)
12
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
13
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
14
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
15
+ def list(status: nil, page: nil, max_items: nil)
14
16
  wrap_paginated(service: "templates", operation: "list", is_mutation: false) do
15
- params = compact_query_params(status: status)
16
- paginate("/templates.json", params: params, operation: "ListTemplates")
17
+ params = compact_query_params(status: status, page: page)
18
+ paginate("/templates.json", params: params, operation: "ListTemplates", max_items: max_items)
17
19
  end
18
20
  end
19
21
 
@@ -9,10 +9,13 @@ module Basecamp
9
9
 
10
10
  # Get project timeline
11
11
  # @param project_id [Integer] project id ID
12
- # @return [Enumerator<Hash>] paginated results
13
- def get_project_timeline(project_id:)
12
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
13
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
14
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
15
+ def get_project_timeline(project_id:, page: nil, max_items: nil)
14
16
  wrap_paginated(service: "timeline", operation: "get_project_timeline", is_mutation: false, project_id: project_id) do
15
- paginate("/projects/#{project_id}/timeline.json", operation: "GetProjectTimeline")
17
+ params = compact_query_params(page: page)
18
+ paginate("/projects/#{project_id}/timeline.json", params: params, operation: "GetProjectTimeline", max_items: max_items)
16
19
  end
17
20
  end
18
21
  end
@@ -12,11 +12,13 @@ module Basecamp
12
12
  # @param from [String, nil] from
13
13
  # @param to [String, nil] to
14
14
  # @param person_id [Integer, nil] person id
15
- # @return [Enumerator<Hash>] paginated results
16
- def for_project(project_id:, from: nil, to: nil, person_id: nil)
15
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
16
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
17
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
18
+ def for_project(project_id:, from: nil, to: nil, person_id: nil, page: nil, max_items: nil)
17
19
  wrap_paginated(service: "timesheets", operation: "for_project", is_mutation: false, project_id: project_id) do
18
- params = compact_query_params(from: from, to: to, person_id: person_id)
19
- paginate("/projects/#{project_id}/timesheet.json", params: params, operation: "GetProjectTimesheet")
20
+ params = compact_query_params(from: from, to: to, person_id: person_id, page: page)
21
+ paginate("/projects/#{project_id}/timesheet.json", params: params, operation: "GetProjectTimesheet", max_items: max_items)
20
22
  end
21
23
  end
22
24
 
@@ -25,11 +27,13 @@ module Basecamp
25
27
  # @param from [String, nil] from
26
28
  # @param to [String, nil] to
27
29
  # @param person_id [Integer, nil] person id
28
- # @return [Enumerator<Hash>] paginated results
29
- def for_recording(recording_id:, from: nil, to: nil, person_id: nil)
30
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
31
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
32
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
33
+ def for_recording(recording_id:, from: nil, to: nil, person_id: nil, page: nil, max_items: nil)
30
34
  wrap_paginated(service: "timesheets", operation: "for_recording", is_mutation: false, resource_id: recording_id) do
31
- params = compact_query_params(from: from, to: to, person_id: person_id)
32
- paginate("/recordings/#{recording_id}/timesheet.json", params: params, operation: "GetRecordingTimesheet")
35
+ params = compact_query_params(from: from, to: to, person_id: person_id, page: page)
36
+ paginate("/recordings/#{recording_id}/timesheet.json", params: params, operation: "GetRecordingTimesheet", max_items: max_items)
33
37
  end
34
38
  end
35
39
 
@@ -78,6 +82,16 @@ module Basecamp
78
82
  http_put("/timesheet_entries/#{entry_id}", body: compact_params(date: date, hours: hours, description: description, person_id: person_id)).json
79
83
  end
80
84
  end
85
+
86
+ # Permanently delete a timesheet entry; answers 403 when the caller may not archive or trash it.
87
+ # @param entry_id [Integer] entry id ID
88
+ # @return [void]
89
+ def destroy(entry_id:)
90
+ with_operation(service: "timesheets", operation: "destroy", is_mutation: true, resource_id: entry_id) do
91
+ http_delete("/timesheet_entries/#{entry_id}")
92
+ nil
93
+ end
94
+ end
81
95
  end
82
96
  end
83
97
  end
@@ -13,17 +13,20 @@ module Basecamp
13
13
  # @return [void]
14
14
  def reposition(group_id:, position:)
15
15
  with_operation(service: "todolistgroups", operation: "reposition", is_mutation: true, resource_id: group_id) do
16
- http_put("/todolists/#{group_id}/position.json", body: compact_params(position: position))
16
+ http_put("/todolists/groups/#{group_id}/position.json", body: compact_params(position: position))
17
17
  nil
18
18
  end
19
19
  end
20
20
 
21
21
  # List groups in a todolist
22
22
  # @param todolist_id [Integer] todolist id ID
23
- # @return [Enumerator<Hash>] paginated results
24
- def list(todolist_id:)
23
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
24
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
25
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
26
+ def list(todolist_id:, page: nil, max_items: nil)
25
27
  wrap_paginated(service: "todolistgroups", operation: "list", is_mutation: false, resource_id: todolist_id) do
26
- paginate("/todolists/#{todolist_id}/groups.json", operation: "ListTodolistGroups")
28
+ params = compact_query_params(page: page)
29
+ paginate("/todolists/#{todolist_id}/groups.json", params: params, operation: "ListTodolistGroups", max_items: max_items)
27
30
  end
28
31
  end
29
32
 
@@ -16,13 +16,13 @@ module Basecamp
16
16
  end
17
17
  end
18
18
 
19
- # Update an existing todolist or todolist group
19
+ # Replace a todolist (or todolist group) with a new complete representation.
20
20
  # @param id [Integer] id ID
21
- # @param name [String, nil] Name (required for both Todolist and TodolistGroup)
22
- # @param description [String, nil] Description (Todolist only, ignored for groups)
21
+ # @param name [String] 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
22
+ # @param description [String, nil] Description (rich text HTML) - writable for a todolist group as well as a todolist, and omitting it clears it either way
23
23
  # @return [Hash] response data
24
- def update(id:, name: nil, description: nil)
25
- with_operation(service: "todolists", operation: "update", is_mutation: true, resource_id: id) do
24
+ def replace(id:, name:, description: nil)
25
+ with_operation(service: "todolists", operation: "replace", is_mutation: true, resource_id: id) do
26
26
  http_put("/todolists/#{id}", body: compact_params(name: name, description: description)).json
27
27
  end
28
28
  end
@@ -41,11 +41,13 @@ module Basecamp
41
41
  # List todolists in a todoset
42
42
  # @param todoset_id [Integer] todoset id ID
43
43
  # @param status [String, nil] active|archived|trashed
44
- # @return [Enumerator<Hash>] paginated results
45
- def list(todoset_id:, status: nil)
44
+ # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
45
+ # @param max_items [Integer, nil] cap on items yielded across pages; nil or non-positive means no cap
46
+ # @return [ListEnumerator<Hash>] lazily paginated results (#meta carries pagination metadata)
47
+ def list(todoset_id:, status: nil, page: nil, max_items: nil)
46
48
  wrap_paginated(service: "todolists", operation: "list", is_mutation: false, resource_id: todoset_id) do
47
- params = compact_query_params(status: status)
48
- paginate("/todosets/#{todoset_id}/todolists.json", params: params, operation: "ListTodolists")
49
+ params = compact_query_params(status: status, page: page)
50
+ paginate("/todosets/#{todoset_id}/todolists.json", params: params, operation: "ListTodolists", max_items: max_items)
49
51
  end
50
52
  end
51
53