Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ Then you should be able to copy/paste any example from the docs. After pasting a
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/999999999/projects.json | json_pp
```


Flat routes
-----------

Every resource in the Basecamp 4 API is uniquely identified by its own ID. You can access any resource directly without including its project (bucket) in the URL:

```
GET /todos/67890.json
GET /messages/12345.json
GET /todolists/456/todos.json
```

These **flat routes** are the canonical form for all new integrations. The project context is derived server-side from the resource itself, so you rarely need to supply a `bucket_id`. A few project-level collection endpoints (message types, webhooks index/create, tools) still require the project-scoped form — these are noted in their respective sections.

For listing and creating resources under a parent, use the parent's ID directly:

```
GET /todolists/456/todos.json # list to-dos in a to-do list
POST /todolists/456/todos.json # create a to-do in a to-do list
GET /message_boards/789/messages.json # list messages on a board
POST /recordings/123/comments.json # comment on any recording
```

The previous project-scoped form — `/buckets/{project_id}/...` — remains available and will continue to work in perpetuity, but is considered legacy. All endpoint documentation in this guide uses flat routes as the primary form, with legacy equivalents noted at the bottom of each section.


Authentication
--------------

Expand Down Expand Up @@ -93,7 +119,7 @@ Most collection APIs paginate their results. The number of requests that'll appe
Here's an example response header from requesting the third page of [messages](sections/messages.md#messages):

```
Link: <https://3.basecampapi.com/999999999/buckets/2085958496/messages.json?page=4>; rel="next"
Link: <https://3.basecampapi.com/999999999/message_boards/3/messages.json?page=4>; rel="next"
```

If the `Link` header is blank, that's the last page. The Basecamp 4 API also provides the `X-Total-Count` header, which displays the total number of resources in the collection you are fetching.
Expand Down Expand Up @@ -145,15 +171,24 @@ Understanding Basecamp's domain model helps you navigate the API effectively.

### The bucket/project relationship

Every project has exactly one **bucket**—its storage container for all content. In API URLs, `bucket_id` and project ID are the same value:
Every project has exactly one **bucket**—its storage container for all content. The `bucket_id` and project ID are the same value.

With flat routes, you don't need to know the bucket ID to access a resource — just use the resource's own ID:

```
GET /todos/67890.json # access a to-do directly
GET /todolists/456/todos.json # list to-dos in a to-do list
```

The legacy project-scoped form includes the bucket explicitly:

```
/buckets/12345/todolists/67890.json
This is the project ID
GET /buckets/12345/todos/67890.json
This is the project ID
```

When you see `/buckets/{id}/...` in the API, think "project." (Templates also have buckets internally, but they use `/templates/...` endpoints.)
Both forms return identical responses. Flat routes are preferred for all new integrations.

### Recordings: bucket contents

Expand Down Expand Up @@ -196,8 +231,8 @@ Project
└── To-do item
```

To create a to-do list, POST to the **to-do set**, not the project:
`POST /buckets/{project_id}/todosets/{todoset_id}/todolists.json`
To create a to-do list, POST to the **to-do set**:
`POST /todosets/{todoset_id}/todolists.json`

### Similar patterns for other tools

Expand Down
54 changes: 32 additions & 22 deletions sections/boosts.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ Endpoints:
Get boosts
----------

* `GET /buckets/1/recordings/2/boosts.json` will return a [paginated list][pagination] of active boosts in the project with an ID of `1` and the recording with ID of `2`.
* `GET /buckets/1/recordings/2/events/3/boosts.json` will return a [paginated list][pagination] of active boosts on the event with ID of `3` on the recording with ID of `2` in the project with an ID of `1`.
* `GET /recordings/2/boosts.json` will return a [paginated list][pagination] of active boosts on the recording with ID of `2`.
* `GET /recordings/2/events/3/boosts.json` will return a [paginated list][pagination] of active boosts on the event with ID of `3` on the recording with ID of `2`.

###### Example JSON Response
<!-- START GET /buckets/1/recordings/2/boosts.json -->
<!-- START GET /recordings/2/boosts.json -->
```json
[
{
"id": 971636438,
"content": "Yay!",
"created_at": "2026-01-31T08:32:05.950Z",
"created_at": "2026-02-12T06:09:35.050Z",
"booster": {
"id": 1049715916,
"attachable_sgid": "BAh7BkkiC19yYWlscwY6BkVUewdJIglkYXRhBjsAVEkiK2dpZDovL2JjMy9QZXJzb24vMTA0OTcxNTkxNj9leHBpcmVzX2luBjsAVEkiCHB1cgY7AFRJIg9hdHRhY2hhYmxlBjsAVA==--6414c77c0a206bc7d45987b8335318b768e8651f",
Expand All @@ -35,14 +35,14 @@ Get boosts
"title": "Senior Branding Strategist",
"bio": null,
"location": null,
"created_at": "2026-01-31T08:29:33.483Z",
"updated_at": "2026-01-31T08:29:33.483Z",
"created_at": "2026-02-12T06:08:51.481Z",
"updated_at": "2026-02-12T06:08:51.481Z",
"admin": false,
"owner": false,
"client": false,
"employee": false,
"time_zone": "America/Chicago",
"avatar_url": "https://3.basecampapi.com/195539477/people/BAhpBMxkkT4=--0ea74d7e5d39ad2d120da79250b179b7e0b00c44/avatar?v=1",
"avatar_url": "https://3.basecampapi.com/195539477/people/BAhpBMxkkT4=--0ea74d7e5d39ad2d120da79250b179b7e0b00c44/avatar",
"can_ping": true,
"can_manage_projects": true,
"can_manage_people": true,
Expand All @@ -52,26 +52,26 @@ Get boosts
}
]
```
<!-- END GET /buckets/1/recordings/2/boosts.json -->
<!-- END GET /recordings/2/boosts.json -->
###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/recordings/2/boosts.json
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCOUNT_ID/recordings/2/boosts.json
```


Get a boost
-----------

* `GET /buckets/1/boosts/2.json` will return the boost with an ID of `2` in the project with an ID of `1`.
* `GET /boosts/2.json` will return the boost with an ID of `2`.

###### Example JSON Response
<!-- START GET /buckets/1/boosts/2.json -->
<!-- START GET /boosts/2.json -->
```json
{
"id": 971636442,
"content": "Congrats!",
"created_at": "2026-01-31T08:32:06.277Z",
"created_at": "2026-02-12T06:09:35.160Z",
"booster": {
"id": 1049715923,
"attachable_sgid": "BAh7BkkiC19yYWlscwY6BkVUewdJIglkYXRhBjsAVEkiK2dpZDovL2JjMy9QZXJzb24vMTA0OTcxNTkyMz9leHBpcmVzX2luBjsAVEkiCHB1cgY7AFRJIg9hdHRhY2hhYmxlBjsAVA==--cd47d2c8e0331392dba60ca41aec78611b6f2f2e",
Expand All @@ -81,14 +81,14 @@ Get a boost
"title": "Principal Configuration Planner",
"bio": null,
"location": null,
"created_at": "2026-01-31T08:29:37.254Z",
"updated_at": "2026-01-31T08:29:37.254Z",
"created_at": "2026-02-12T06:08:53.796Z",
"updated_at": "2026-02-12T06:08:53.796Z",
"admin": false,
"owner": false,
"client": false,
"employee": false,
"time_zone": "America/Chicago",
"avatar_url": "https://3.basecampapi.com/195539477/people/BAhpBNNkkT4=--e3c2676dde30e7c13f87642e3a3dd46ad657f731/avatar?v=1",
"avatar_url": "https://3.basecampapi.com/195539477/people/BAhpBNNkkT4=--e3c2676dde30e7c13f87642e3a3dd46ad657f731/avatar",
"can_ping": true,
"can_manage_projects": true,
"can_manage_people": true,
Expand All @@ -104,19 +104,19 @@ Get a boost
}
}
```
<!-- END GET /buckets/1/boosts/2.json -->
<!-- END GET /boosts/2.json -->
###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/boosts/2.json
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCOUNT_ID/boosts/2.json
```


Create a boost
--------------

* `POST /buckets/1/recordings/2/boosts.json` publishes a boost in the project with ID `1` and under the recording with an ID of `2`.
* `POST /buckets/1/recordings/2/events/3/boosts.json` publishes a boost on the event with ID `3` on the recording with ID of `2` in the project with ID `1`. Only boostable events (`completed`, `adopted`, `column_changed`) accept boosts; other events will return `403 Forbidden`.
* `POST /recordings/2/boosts.json` publishes a boost on the recording with an ID of `2`.
* `POST /recordings/2/events/3/boosts.json` publishes a boost on the event with ID `3` on the recording with ID of `2`. Only boostable events (`completed`, `adopted`, `column_changed`) accept boosts; other events will return `403 Forbidden`.

**Required parameters**: `content` as the body of the boost.

Expand All @@ -135,24 +135,34 @@ This endpoint will return `201 Created` with the current JSON representation of
```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"content":"Great work!"}' \
https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/recordings/2/boosts.json
https://3.basecampapi.com/$ACCOUNT_ID/recordings/2/boosts.json
```


Destroy a boost
---------------

* `DELETE /buckets/1/boosts/2.json` will destroy the boost with an ID of `2` in the project with an ID of `1`.
* `DELETE /boosts/2.json` will destroy the boost with an ID of `2`.

Only the boost's creator or an admin can destroy a boost. Returns `204 No Content` on success, or `403 Forbidden` if the authenticated user does not have permission.

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \
https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/boosts/2.json
https://3.basecampapi.com/$ACCOUNT_ID/boosts/2.json
```


Comment thread
jeremy marked this conversation as resolved.
Legacy project-scoped routes
-----------------------------

The following project-scoped routes are still supported and will remain available, but flat routes above are the canonical form for new integrations.

* `GET /buckets/1/recordings/2/boosts.json` → [Get boosts](#get-boosts)
* `GET /buckets/1/boosts/2.json` → [Get a boost](#get-a-boost)
* `POST /buckets/1/recordings/2/boosts.json` → [Create a boost](#create-a-boost)
* `DELETE /buckets/1/boosts/2.json` → [Destroy a boost](#destroy-a-boost)

[events]: https://github.com/basecamp/bc3-api/blob/master/sections/events.md#events
[pagination]: https://github.com/basecamp/bc3-api/blob/master/README.md#pagination
Loading