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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ API endpoints
- [Cloud files](sections/cloud_files.md#cloud-files)
- [Comments](sections/comments.md#comments)
- [Documents](sections/documents.md#documents)
- [Drafts](sections/drafts.md#drafts)
- [Events](sections/events.md#events)
- [Everything](sections/everything.md#everything)
- [External links](sections/external_links.md#external-links)
Expand Down
9 changes: 6 additions & 3 deletions sections/card_table_cards.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,23 +392,26 @@ _Optional parameters_:
* `title` - of the card.
* `content` - containing information about the card. See our [Rich text guide][rich] for what HTML tags are allowed.
* `due_on` - due date (ISO 8601) of the card
* `assignee_ids` - an array of people that will be assigned to this to-do. Please see the [Get people][people] endpoints to retrieve them.
* `assignee_ids` - an array of people that will be assigned to this card. Please see the [Get people][people] endpoints to retrieve them.

**Note:** omitting `title`, `content`, or `assignee_ids` leaves those values unchanged, but omitting `due_on` **clears** the due date. To keep an existing due date while changing something else, resend the current `due_on`.

This endpoint will return `200 OK` with the current JSON representation of the card if the update was a success. See the [Get a card](#get-a-card) endpoint for more info on the payload.

###### Example JSON Request

```json
{
"title": "Updated investigation"
"title": "Updated investigation",
"due_on": "2016-05-01"
}
```

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"title":"Updated investigation"}' -X PUT \
-d '{"title":"Updated investigation","due_on":"2016-05-01"}' -X PUT \
https://3.basecampapi.com/$ACCOUNT_ID/card_tables/cards/2.json
```

Expand Down
10 changes: 7 additions & 3 deletions sections/card_table_steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,30 @@ Update a step

_Optional parameters_:

* `title` - of the card.
* `title` - of the step.
* `due_on` - due date (ISO 8601) of the step.
* `assignee_ids` - an array of people ids that will be assigned to this step. Please see the [Get people][people] endpoints to retrieve them.
* `assignees` - a comma separated list of people ids (legacy format, prefer `assignee_ids`).

**Note:** this update **replaces** the step — any of `title`, `due_on`, or `assignee_ids` you omit is cleared. Resend the values you want to keep.

This endpoint will return `200 OK` with the current JSON representation of the step if the update was a success. See the step property of the [Get a card][card] endpoint for more info on the payload.

###### Example JSON Request

```json
{
"title": "Updated inspiration"
"title": "Updated inspiration",
"due_on": "2021-01-01",
"assignee_ids": [30068628, 270913789]
}
```

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"title": "Updated inspiration"}' -X PUT \
-d '{"title": "Updated inspiration", "due_on": "2021-01-01", "assignee_ids": [30068628, 270913789]}' -X PUT \
https://3.basecampapi.com/$ACCOUNT_ID/card_tables/steps/2.json
```

Expand Down
4 changes: 2 additions & 2 deletions sections/client_approvals.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ _Optional query parameters_:
"title": "The Clientside",
"type": "Client::Board",
"url": "https://3.basecampapi.com/195539477/buckets/2085958506/client/board.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board.1069480059"
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board"
},
"bucket": {
"id": 2085958506,
Expand Down Expand Up @@ -144,7 +144,7 @@ Get a client approval
"title": "The Clientside",
"type": "Client::Board",
"url": "https://3.basecampapi.com/195539477/buckets/2085958506/client/board.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board.1069480059"
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board"
},
"bucket": {
"id": 2085958506,
Expand Down
4 changes: 2 additions & 2 deletions sections/client_correspondences.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ _Optional query parameters_:
"title": "The Clientside",
"type": "Client::Board",
"url": "https://3.basecampapi.com/195539477/buckets/2085958506/client/board.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board.1069480059"
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board"
},
"bucket": {
"id": 2085958506,
Expand Down Expand Up @@ -114,7 +114,7 @@ Get a client correspondence
"title": "The Clientside",
"type": "Client::Board",
"url": "https://3.basecampapi.com/195539477/buckets/2085958506/client/board.json",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board.1069480059"
"app_url": "https://3.basecamp.com/195539477/buckets/2085958506/client/board"
},
"bucket": {
"id": 2085958506,
Expand Down
20 changes: 18 additions & 2 deletions sections/documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCO
Create a document
-----------------

* `POST /vaults/2/documents.json` publishes a document under the vault with an ID of `2`.
* `POST /vaults/2/documents.json` creates a document under the vault with an ID of `2`.

**Required parameters**: `title` as the title of the document, and `content` as the body of the document. See our [Rich text guide][rich] for what HTML tags are allowed.

_Optional parameters_:

* `status`, set to `active` to publish immediately.
* `status`set to `active` to publish the document immediately. **When `status` is omitted the document is created as a draft** (it defaults to `drafted`); a draft is not posted and notifies no one until you publish it. See [Publishing a draft](#publishing-a-draft).
* `visible_to_clients` - top-level boolean. When the project has clients enabled, whether the document is visible to them. Defaults to `false` (team callers creating directly under the docked tool); a **client** caller always creates client-visible records. Applies only when creating directly in the tool's vault; items created inside a folder inherit the folder's visibility. See [Client visibility][client_visibility] to change it after creation.

This endpoint will return `201 Created` with the current JSON representation of the document if the creation was a success. See the [Get a document](#get-a-document) endpoint for more info on the payload.
Expand Down Expand Up @@ -218,6 +218,22 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/j
https://3.basecampapi.com/$ACCOUNT_ID/documents/2.json
```

Publishing a draft
------------------

Publish a [draft](drafts.md) document by updating it with `status` set to `active`. Unlike a message, a document update **replaces** the document rather than merging, so you must resend `title` and `content` alongside `status`:

```json
{
"title": "New Hire Info",
"content": "<div><strong>Getting started</strong></div>",
"status": "active"
}
```

A status-only update fails with `400 Bad Request` (the `document` parameters are required), and omitting a field clears its value. Publishing posts the document and notifies its subscribers exactly once — the same publication side-effects as [publishing a message](messages.md#publishing-a-draft).


Legacy project-scoped routes
-----------------------------

Expand Down
64 changes: 64 additions & 0 deletions sections/drafts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Drafts
======

Endpoint for retrieving the current user's unpublished message, document, upload, and client approval/correspondence drafts across their active projects.

A draft is a [message](messages.md), [document](documents.md), file [upload](uploads.md), or client [approval](client_approvals.md)/[correspondence](client_correspondences.md) that has been saved but not yet published. Messages and documents can be drafted and published through the API — see the [messages](messages.md#create-a-message) and [documents](documents.md#create-a-document) sections. Uploads have their own [create](uploads.md#create-an-upload) and [update](uploads.md#update-an-upload) endpoints, but no documented way to create or publish a *draft* upload; client approvals and correspondences are read-only over the API. Drafts of all of these types still appear here, however they were authored.

Endpoints:

- [Get my drafts](#get-my-drafts)


Get my drafts
-------------

* `GET /my/drafts.json` will return a [paginated list][pagination] of the current user's drafts, most recently updated first, regardless of how they were created. Drafts whose project has been archived or trashed are excluded.

Five kinds of drafts are returned:

* [Messages](messages.md)
* [Documents](documents.md)
* [Uploads](uploads.md)
* Client [approvals](client_approvals.md)
* Client [correspondences](client_correspondences.md)

**Not returned:** other resources can also be saved in a drafted state, but they aren't part of this list — notably [Google documents](google_documents.md) (which default to `drafted` when `status` is omitted), [cloud files](cloud_files.md), and [schedule entries](schedule_entries.md). This endpoint is not an exhaustive feed of everything you have unpublished.

Each draft carries an `excerpt`: up to 300 characters of plain-text content, or an empty string when the draft has no body yet. `parent` is `null` for drafts filed directly under their bucket, and `scheduled_posting_at` is `null` unless the draft is scheduled to publish later.

###### Example JSON Response
<!-- START GET /my/drafts.json -->
```json
[
{
"id": 1069480291,
"app_url": "https://3.basecamp.com/195539477/buckets/2085958504/messages/1069480291",
"title": "Launch announcement",
"type": "message",
"bucket": {
"id": 2085958504,
"name": "The Leto Laptop",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958504"
},
"parent": {
"id": 1069479829,
"title": "Message Board",
"app_url": "https://3.basecamp.com/195539477/buckets/2085958504/message_boards/1069479829"
},
"excerpt": "Still polishing the wording before we post this.",
"created_at": "2026-07-22T22:11:07.611Z",
"updated_at": "2026-07-22T22:11:07.632Z",
"scheduled_posting_at": null
}
]
```
<!-- END GET /my/drafts.json -->

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCOUNT_ID/my/drafts.json
```

[pagination]: ../README.md#pagination
21 changes: 18 additions & 3 deletions sections/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,13 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCO
Create a message
----------------

* `POST /message_boards/3/messages.json` publishes a message under the message board with an ID of `3`.
* `POST /message_boards/3/messages.json` creates a message under the message board with an ID of `3`.

**Required parameters**: `subject` as the title of the message, and `status`, set to `active` to publish immediately.
**Required parameters**: `subject` as the title of the message.

_Optional parameters_:
_Optional parameters_:

* `status` — set to `active` to publish the message immediately. **When `status` is omitted the message is created as a draft** (it defaults to `drafted`); a draft is not posted and notifies no one until you publish it. See [Publishing a draft](#publishing-a-draft).
* `content` as the body of the message. See our [Rich text guide][4] for what HTML tags are allowed.
* `category_id` to set a type for the message. To get a list of all the message types for a project, see the [Get message types][5] endpoint.
* `subscriptions` an array of people IDs to be notified of the new message when it's posted and subscribed to it. If not present, all people on the project will be notified and subscribed.
Expand Down Expand Up @@ -470,6 +471,20 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/j
https://3.basecampapi.com/$ACCOUNT_ID/messages/2.json
```

Publishing a draft
------------------

Publish a [draft](drafts.md) message by updating it with `status` set to `active`:

```json
{ "status": "active" }
```

A message update **merges** the fields you send, so you don't need to resend `subject` or `content` — the draft's existing content is preserved.

Publishing is what actually posts the message, and it happens exactly once: each subscriber who should hear about it receives a single notification (delivered shortly after, once notifications are dispatched). Creating or re-saving a draft notifies no one — only publishing does. (Documents publish the same way; see [Publishing a draft](documents.md#publishing-a-draft) in the documents section.)


Pin a message
----------------

Expand Down
105 changes: 102 additions & 3 deletions sections/my_assignments.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
My assignments
===============

Endpoints for retrieving the current user's assignments, including active assignments split by priority, completed assignments, and due assignments filtered by time scope.
Retrieve the current user's assignments active ones split by priority, completed ones, and due ones filtered by time scope — and manage their priority order ("Up Next"). For how to actually change an assignment (assign someone, complete it, reschedule it), see [Act on assignments](#act-on-assignments), which routes you to the to-do, card, and card table step endpoints.

Each assignment includes its content, type, bucket, parent, assignees, due dates, completion status, and any child assignments (e.g. card steps nested under their parent card).
Each assignment includes its content, type, bucket, parent, assignees, due dates, completion status, and any child assignments (e.g. card table steps nested under their parent card).

**Note:** These endpoints return the full result set and are not paginated.
**Note:** the retrieval endpoints below — Get assignments, Get completed assignments, and Get due assignments — return the full result set and are not paginated.

Endpoints:

- [Get assignments](#get-assignments)
- [Get completed assignments](#get-completed-assignments)
- [Get due assignments](#get-due-assignments)
- [Prioritize an assignment](#prioritize-an-assignment)
- [Deprioritize an assignment](#deprioritize-an-assignment)
- [Reorder Up Next](#reorder-up-next)
Comment thread
jeremy marked this conversation as resolved.


Get assignments
Expand Down Expand Up @@ -635,3 +638,99 @@ Providing an invalid `scope` returns `400 Bad Request` with an error listing the
```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://3.basecampapi.com/$ACCOUNT_ID/my/assignments/due.json?scope=overdue
```


Act on assignments
------------------

Assignments are created and changed through the to-do, card, and card table step endpoints, not through `/my/assignments`. A card table step is returned nested under its parent card as a `children` entry, and it's acted on through its own step endpoints, not the parent card's:

* **Assign or unassign** — pass `assignee_ids` when updating a [to-do](todos.md#update-a-to-do) or a [card](card_table_cards.md#update-a-card); include `notify: true` to notify the people you add. A card table step is assigned on the step itself via [Update a step](card_table_steps.md#update-a-step) (`PUT /card_tables/steps/:id.json`), not the parent card.
* **Complete or reopen** — a to-do uses its [completion](todos.md#complete-a-to-do) endpoint (`POST`/`DELETE /todos/:id/completion.json`). A card table step uses [Change step completion status](card_table_steps.md#change-step-completion-status) — `PUT /card_tables/steps/:step_id/completions.json` with `completion: "on"` to complete or `"off"` to reopen. A card has no completion endpoint: it's completed by moving it into a done column and reopened by moving it out, via [Move a card](card_table_cards.md#move-a-card) (`POST /card_tables/cards/:id/moves.json` with the destination `column_id`).
* **Reschedule** — update a [to-do](todos.md#update-a-to-do) with `due_on` and, optionally, `starts_on`. A [card](card_table_cards.md#update-a-card) is rescheduled with `due_on` only — cards have no `starts_on` — and a card table [step](card_table_steps.md#update-a-step) likewise takes `due_on`, on its own update endpoint.

**Note:** these updates replace omitted fields. A to-do update and a card table step update both clear any field you don't send — including `assignee_ids`, `due_on`, and (for steps) `title` — so resend the current values you want to keep. A card update is the one that preserves omitted `title` and `content`, but even there `due_on` is cleared unless you resend it. In short: to change only assignees or only the due date, echo the record's other current fields in the same request.
Comment thread
jeremy marked this conversation as resolved.


Prioritize an assignment
------------------------

* `POST /my/priorities.json` adds a recording to "Up Next" — the current user's ordered list of prioritized assignments, returned as `priorities` in [Get assignments](#get-assignments). Returns `204 No Content`.

Identify the item by the **recording id** of the thing that carries the priority, not by the assignment id. [Get assignments](#get-assignments) normalizes a card table step under its parent card, so which id to send depends on what you're prioritizing:

* **A to-do, or a card itself** — send the item's top-level `id`.
* **A card table step that isn't prioritized yet** — send the step's own `id` from its parent card's `children`. Only already-prioritized top-level entries carry a `priority_recording_id`, so a step you haven't prioritized has just its own id.
* **A step that is already prioritized** — the top-level entry's `id` is the *card's*, so send that entry's `priority_recording_id`, which points at the prioritized step. Use it to reorder or deprioritize.

If more than one step on the same card is prioritized, [Get assignments](#get-assignments) lists that card once and reports a single `priority_recording_id`; the sibling step priorities aren't separately addressable through this list.

**Required parameters**: `id` — the recording id to prioritize.

###### Example JSON Request

```json
{
"id": 1069479801
}
```

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1069479801}' \
https://3.basecampapi.com/$ACCOUNT_ID/my/priorities.json
```


Deprioritize an assignment
--------------------------

* `DELETE /my/priorities/1069479801.json` removes the recording with an ID of `1069479801` from Up Next. Returns `204 No Content`.

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-X DELETE \
https://3.basecampapi.com/$ACCOUNT_ID/my/priorities/1069479801.json
```


Reorder Up Next
---------------

* `POST /my/priority_moves.json` moves an already-prioritized recording to a new spot in Up Next. Returns `204 No Content`.

**Required parameters**:

* `source_id` — the recording id to move, chosen the same way as when [prioritizing](#prioritize-an-assignment).
* `position` — the 1-based position to move it to.

###### Example JSON Request

```json
{
"source_id": 1069479801,
"position": 1
}
```

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"source_id":1069479801,"position":1}' \
https://3.basecampapi.com/$ACCOUNT_ID/my/priority_moves.json
```

Reordering validates `position` and, on failure, returns an error body of the form `{ "error": "..." }`:

* Missing `position` → `400 Bad Request`, `{ "error": "Position is required." }`.
* Non-integer `position` → `400 Bad Request`, `{ "error": "Position must be an integer." }`.
* `position` outside the list → `422 Unprocessable Entity`, `{ "error": "Position must be between 1 and N." }`, where `N` is the number of prioritized assignments.
* A recording that isn't prioritized → `422 Unprocessable Entity`, `{ "error": "Recording is not prioritized." }`.
* A recording you can't access → `404 Not Found`.
Loading