Skip to content
Open
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
59 changes: 37 additions & 22 deletions sections/campfires.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ Get Campfires
"status": "active",
"visible_to_clients": false,
"created_at": "2026-01-31T08:29:58.785Z",
"updated_at": "2026-01-31T08:30:23.083Z",
"updated_at": "2026-02-07T01:42:53.738Z",
"title": "Chat",
"inherits_status": true,
"type": "Chat::Transcript",
"url": "https://3.basecampapi.com/195539477/buckets/2085958502/chats/1069478985.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958502/chats/1069478985",
"bookmark_url": "https://3.basecampapi.com/195539477/my/bookmarks/BAh7BkkiC19yYWlscwY6BkVUewdJIglkYXRhBjsAVEkiLmdpZDovL2JjMy9SZWNvcmRpbmcvMTA2OTQ3ODk4NT9leHBpcmVzX2luBjsAVEkiCHB1cgY7AFRJIg1yZWFkYWJsZQY7AFQ=--f745f0a5dc8108b7f361e578d47b72ff974ed50e.json",
"subscription_url": "https://3.basecampapi.com/195539477/buckets/2085958502/recordings/1069478985/subscription.json",
"subscription_url": "https://3.basecampapi.com/195539477/recordings/1069478985/subscription.json",
"position": 4,
"bucket": {
"id": 2085958502,
Expand Down Expand Up @@ -66,7 +66,7 @@ Get Campfires
"can_access_hill_charts": true
},
"topic": "Chat",
"lines_url": "https://3.basecampapi.com/195539477/buckets/2085958502/chats/1069478985/lines.json"
"lines_url": "https://3.basecampapi.com/195539477/chats/1069478985/lines.json"
}
]
```
Expand All @@ -80,24 +80,24 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCO
Get a Campfire
--------------

* `GET /buckets/1/chats/2.json` will return the Campfire with ID `2` in the project with ID `1`.
* `GET /chats/2.json` will return the Campfire with ID `2`.

###### Example JSON Response
<!-- START GET /buckets/1/chats/2.json -->
<!-- START GET /chats/2.json -->
```json
{
"id": 1069478985,
"status": "active",
"visible_to_clients": false,
"created_at": "2026-01-31T08:29:58.785Z",
"updated_at": "2026-01-31T08:30:23.083Z",
"updated_at": "2026-02-07T01:42:53.738Z",
"title": "Chat",
"inherits_status": true,
"type": "Chat::Transcript",
"url": "https://3.basecampapi.com/195539477/buckets/2085958502/chats/1069478985.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958502/chats/1069478985",
"bookmark_url": "https://3.basecampapi.com/195539477/my/bookmarks/BAh7BkkiC19yYWlscwY6BkVUewdJIglkYXRhBjsAVEkiLmdpZDovL2JjMy9SZWNvcmRpbmcvMTA2OTQ3ODk4NT9leHBpcmVzX2luBjsAVEkiCHB1cgY7AFRJIg1yZWFkYWJsZQY7AFQ=--f745f0a5dc8108b7f361e578d47b72ff974ed50e.json",
"subscription_url": "https://3.basecampapi.com/195539477/buckets/2085958502/recordings/1069478985/subscription.json",
"subscription_url": "https://3.basecampapi.com/195539477/recordings/1069478985/subscription.json",
"position": 4,
"bucket": {
"id": 2085958502,
Expand Down Expand Up @@ -132,23 +132,23 @@ Get a Campfire
"can_access_hill_charts": true
},
"topic": "Chat",
"lines_url": "https://3.basecampapi.com/195539477/buckets/2085958502/chats/1069478985/lines.json"
"lines_url": "https://3.basecampapi.com/195539477/chats/1069478985/lines.json"
}
```
<!-- END GET /buckets/1/chats/2.json -->
<!-- END GET /chats/2.json -->
###### Copy as cURL

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

Get Campfire lines
------------------

* `GET /buckets/1/chats/2/lines.json` will return a [paginated list][pagination] of Campfire lines of the Campfire with ID `2` in the project with ID `1`.
* `GET /chats/2/lines.json` will return a [paginated list][pagination] of Campfire lines of the Campfire with ID `2`.

###### Example JSON Response
<!-- START GET /buckets/1/chats/2/lines.json -->
<!-- START GET /chats/2/lines.json -->
```json
[
{
Expand All @@ -163,6 +163,8 @@ Get Campfire lines
"url": "https://3.basecampapi.com/195539477/buckets/2085958502/chats/1069478985/lines/1069479068.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958502/chats/1069478985@1069479068",
"bookmark_url": "https://3.basecampapi.com/195539477/my/bookmarks/BAh7BkkiC19yYWlscwY6BkVUewdJIglkYXRhBjsAVEkiLmdpZDovL2JjMy9SZWNvcmRpbmcvMTA2OTQ3OTA2OD9leHBpcmVzX2luBjsAVEkiCHB1cgY7AFRJIg1yZWFkYWJsZQY7AFQ=--ed75de023587361ebed66cce122997d56faeaa2e.json",
"boosts_count": 0,
"boosts_url": "https://3.basecampapi.com/195539477/buckets/2085958502/recordings/1069479068/boosts.json",
"parent": {
"id": 1069478985,
"title": "Chat",
Expand Down Expand Up @@ -202,20 +204,20 @@ Get Campfire lines
}
]
```
<!-- END GET /buckets/1/chats/2/lines.json -->
<!-- END GET /chats/2/lines.json -->
###### Copy as cURL

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

Get a Campfire line
-------------------

* `GET /buckets/1/chats/2/lines/3.json` will return the Campfire line with ID `3` in the Campfire with ID `2` in the project with ID `1`.
* `GET /chats/2/lines/3.json` will return the Campfire line with ID `3` in the Campfire with ID `2`.

###### Example JSON Response
<!-- START GET /buckets/1/chats/2/lines/3.json -->
<!-- START GET /chats/2/lines/3.json -->
```json
{
"id": 1069479054,
Expand All @@ -229,6 +231,8 @@ Get a Campfire line
"url": "https://3.basecampapi.com/195539477/buckets/2085958502/chats/1069478985/lines/1069479054.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958502/chats/1069478985@1069479054",
"bookmark_url": "https://3.basecampapi.com/195539477/my/bookmarks/BAh7BkkiC19yYWlscwY6BkVUewdJIglkYXRhBjsAVEkiLmdpZDovL2JjMy9SZWNvcmRpbmcvMTA2OTQ3OTA1ND9leHBpcmVzX2luBjsAVEkiCHB1cgY7AFRJIg1yZWFkYWJsZQY7AFQ=--bf7b55e226a9c3111e0a8639faa1012b4db18bb1.json",
"boosts_count": 0,
"boosts_url": "https://3.basecampapi.com/195539477/buckets/2085958502/recordings/1069479054/boosts.json",
"parent": {
"id": 1069478985,
"title": "Chat",
Expand Down Expand Up @@ -267,17 +271,17 @@ Get a Campfire line
"content": "👏 🎉"
}
```
<!-- END GET /buckets/1/chats/2/lines/3.json -->
<!-- END GET /chats/2/lines/3.json -->
###### Copy as cURL

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

Create a Campfire line
----------------------

* `POST /buckets/1/chats/2/lines.json` creates a line in the Campfire with ID `2` in the project with ID `1`.
* `POST /chats/2/lines.json` creates a line in the Campfire with ID `2`.

**Required parameters**: `content` as the plain text body for the Campfire line.

Expand All @@ -296,20 +300,31 @@ 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":"Good morning"}' \
https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/chats/2/lines.json
https://3.basecampapi.com/$ACCOUNT_ID/chats/2/lines.json
```

Delete a Campfire line
----------------------

* `DELETE /buckets/1/chats/2/lines/3.json` will delete the Campfire line with ID `3` in the Campfire with ID `2` in the project with ID `1`.
* `DELETE /chats/2/lines/3.json` will delete the Campfire line with ID `3` in the Campfire with ID `2`.

Returns `204 No Content` if successful.

###### Copy as cURL

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

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/chats/2.json` → [Get a Campfire](#get-a-campfire)
* `GET /buckets/1/chats/2/lines.json` → [Get Campfire lines](#get-campfire-lines)
* `GET /buckets/1/chats/2/lines/3.json` → [Get a Campfire line](#get-a-campfire-line)
* `POST /buckets/1/chats/2/lines.json` → [Create a Campfire line](#create-a-campfire-line)
* `DELETE /buckets/1/chats/2/lines/3.json` → [Delete a Campfire line](#delete-a-campfire-line)

[pagination]: https://github.com/basecamp/bc3-api/blob/master/README.md#pagination
40 changes: 26 additions & 14 deletions sections/card_table_cards.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ Endpoints:
Get cards in a column
--------------------

* `GET /buckets/1/card_tables/lists/3/cards.json` will return a [paginated list][pagination] of cards in the project with an ID of `1` and the column with ID of `3`.
* `GET /card_tables/lists/3/cards.json` will return a [paginated list][pagination] of cards in the column with ID of `3`.

###### Example JSON Response
<!-- START GET /buckets/1/card_tables/lists/3/cards.json -->
<!-- START GET /card_tables/lists/3/cards.json -->
```json
[
{
Expand Down Expand Up @@ -85,20 +85,20 @@ Get cards in a column
}
]
```
<!-- END GET /buckets/1/card_tables/lists/3/cards.json -->
<!-- END GET /card_tables/lists/3/cards.json -->
###### Copy as cURL

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

Get a card
--------------------

* `GET /buckets/1/card_tables/cards/2.json` will return the card with an ID of `2` in the project with an ID of `1`.
* `GET /card_tables/cards/2.json` will return the card with an ID of `2`.

###### Example JSON Response
<!-- START GET /buckets/1/card_tables/cards/2.json -->
<!-- START GET /card_tables/cards/2.json -->
```json
{
"id": 1069479602,
Expand Down Expand Up @@ -248,17 +248,17 @@ Get a card
]
}
```
<!-- END GET /buckets/1/card_tables/cards/2.json -->
<!-- END GET /card_tables/cards/2.json -->
###### Copy as cURL

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

Create a card
-------------------------

* `POST /buckets/1/card_tables/lists/2/cards.json` creates a card within the column with ID `2` in the project with id `1`.
* `POST /card_tables/lists/2/cards.json` creates a card within the column with ID `2`.

**Required parameters**: `title` of the card.

Expand All @@ -285,13 +285,13 @@ 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 '{"title":"Investigation", "content": "Investigate on an issue in our todo list.", "due_on": "2021-01-01"}' \
https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/card_tables/lists/2/cards.json
https://3.basecampapi.com/$ACCOUNT_ID/card_tables/lists/2/cards.json
```

Update a card
-----------------------

* `PUT /buckets/1/card_tables/cards/2.json` allows changing of the card with an ID of `2` in the project with ID `1`.
* `PUT /card_tables/cards/2.json` allows changing of the card with an ID of `2`.

_Optional parameters_:

Expand All @@ -315,13 +315,13 @@ This endpoint will return `200 OK` with the current JSON representation of the c
```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"title":"Updated investigation"}' -X PUT \
https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/card_tables/cards/2.json
https://3.basecampapi.com/$ACCOUNT_ID/card_tables/cards/2.json
```

Move a card
-----------------------

* `POST /buckets/1/card_tables/cards/2/moves.json` allows moving of a card with id `2` in the project with ID `1`.
* `POST /card_tables/cards/2/moves.json` allows moving of a card with id `2`.

**Required parameters**: `column_id` of the column destination

Expand All @@ -339,9 +339,21 @@ This endpoint will return `204 No Content` if the update was a success.
```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"column_id": 3}' \
https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/card_tables/cards/2/moves.json
https://3.basecampapi.com/$ACCOUNT_ID/card_tables/cards/2/moves.json
```


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/card_tables/lists/3/cards.json` → [Get cards in a column](#get-cards-in-a-column)
* `GET /buckets/1/card_tables/cards/2.json` → [Get a card](#get-a-card)
* `POST /buckets/1/card_tables/lists/2/cards.json` → [Create a card](#create-a-card)
* `PUT /buckets/1/card_tables/cards/2.json` → [Update a card](#update-a-card)
* `POST /buckets/1/card_tables/cards/2/moves.json` → [Move a card](#move-a-card)

[pagination]: https://github.com/basecamp/bc3-api/blob/master/README.md#pagination
[rich]: https://github.com/basecamp/bc3-api/blob/master/sections/rich_text.md
[people]: https://github.com/basecamp/bc3-api/blob/master/sections/people.md#get-all-people
Loading