From 64facc570510b39721bcfb92c53eef7525a86d6b Mon Sep 17 00:00:00 2001 From: jeissonneira Date: Thu, 20 Aug 2026 15:54:49 -0500 Subject: [PATCH 1/5] Document joining an all-access project --- sections/people.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sections/people.md b/sections/people.md index c8e1796..b49b35b 100644 --- a/sections/people.md +++ b/sections/people.md @@ -6,6 +6,7 @@ Endpoints: - [Get all people](#get-all-people) - [Get people on a project](#get-people-on-a-project) - [Update who can access a project](#update-who-can-access-a-project) +- [Join a project](#join-a-project) - [Enroll people](#enroll-people) - [Add and remove clients on a project](#add-and-remove-clients-on-a-project) - [Enable or disable clients on a project](#enable-or-disable-clients-on-a-project) @@ -212,6 +213,28 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/j ``` +Join a project +-------------- + +* `POST /buckets/1/admissions.json` joins the current person to the project with the given ID, when that project's admissions policy allows it (all-access: `team` or `employee`). + +No parameters. No request body. + +This is the API equivalent of opening an all-access project in the web app and joining it yourself. It is **not** the same as [Update who can access a project](#update-who-can-access-a-project), which grants or revokes other people. + +Returns `201 Created` with an empty body if the join succeeded. After a successful join, the project appears in [Get all projects](projects.md#get-projects) and [Get a project](projects.md#get-a-project) works for this person. + +If the person is not allowed to join (invite-only project, or a client on a `team` project), the API returns `403 Forbidden` with an error such as `"You must first seek admission"`. + +`GET /projects.json` only lists projects the current person has already joined. All-access projects they have not joined yet do not appear there. Use this endpoint with a known project ID (from a URL, a dashboard, or an admin) to join, then list again. + +###### Copy as cURL + +```shell +curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X POST \ + https://3.basecampapi.com/$ACCOUNT_ID/buckets/1/admissions.json +``` + Enroll people ------------- From 678baf006fb9c581803a6e8367ee5bc03214ef9c Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 17 Sep 2026 07:03:05 +0200 Subject: [PATCH 2/5] Match the join-a-project docs to bc3's admissions controller A person who isn't admissible gets 404, not 403. Someone who already has access is redirected to the project. The 403 "seek admission" response comes from reading inside an unjoined all-access project, and carries the URL to join. Also lists which admissions policy admits whom, and fixes the link to Get all projects. --- sections/people.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/sections/people.md b/sections/people.md index b49b35b..e2460cf 100644 --- a/sections/people.md +++ b/sections/people.md @@ -216,17 +216,28 @@ curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/j Join a project -------------- -* `POST /buckets/1/admissions.json` joins the current person to the project with the given ID, when that project's admissions policy allows it (all-access: `team` or `employee`). +* `POST /buckets/1/admissions.json` joins the current person to the project with an ID of `1`, when the project's `admissions` policy lets them in: + * `team` - any member of the account's team (not clients). + * `employee` - team members who belong to the account's own company. + * `invite` - only the account owner. Everyone else has to be added with [Update who can access a project](#update-who-can-access-a-project). No parameters. No request body. -This is the API equivalent of opening an all-access project in the web app and joining it yourself. It is **not** the same as [Update who can access a project](#update-who-can-access-a-project), which grants or revokes other people. +This is the API equivalent of opening an all-access project in the web app and joining it yourself. It is **not** the same as [Update who can access a project](#update-who-can-access-a-project), which grants or revokes access for other people. -Returns `201 Created` with an empty body if the join succeeded. After a successful join, the project appears in [Get all projects](projects.md#get-projects) and [Get a project](projects.md#get-a-project) works for this person. +Returns `201 Created` with an empty body if the join succeeded. After a successful join, the project appears in [Get all projects](projects.md#get-all-projects) and [Get a project](projects.md#get-a-project) works for this person. -If the person is not allowed to join (invite-only project, or a client on a `team` project), the API returns `403 Forbidden` with an error such as `"You must first seek admission"`. +If the project doesn't exist, or its policy doesn't let the current person in (for example, a client, or anyone other than the owner on an `invite` project), the API returns `404 Not Found`. If the person already has access, nothing changes and the response is a `302 Found` redirect to the project. -`GET /projects.json` only lists projects the current person has already joined. All-access projects they have not joined yet do not appear there. Use this endpoint with a known project ID (from a URL, a dashboard, or an admin) to join, then list again. +`GET /projects.json` only lists projects the current person has already joined, so all-access projects they haven't joined yet don't appear there. Reading a resource inside such a project returns `403 Forbidden` with the URL to join: + +```json +{ + "message": "You must first seek admission", + "admission_url": "https://3.basecampapi.com/195539477/buckets/2085958499/admissions", + "admission_method": "POST" +} +``` ###### Copy as cURL From 54112893a5f36848c821100574ecb7756b7e6aaf Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 17 Sep 2026 07:07:44 +0200 Subject: [PATCH 3/5] Say who gets 404 or 302 from joining a project, and how writes join The policy is checked before access, so someone already on a project the policy doesn't admit them to gets 404, not a redirect. admission_url has no .json, so POST it as JSON to get 201. Writes inside a joinable all-access project join implicitly. Owners, not the owner. Agent tokens get 403. projects.md's admissions values now say who can join, and employee is the narrower one. --- sections/people.md | 10 ++++++---- sections/projects.md | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/sections/people.md b/sections/people.md index e2460cf..9db4f3f 100644 --- a/sections/people.md +++ b/sections/people.md @@ -219,17 +219,17 @@ Join a project * `POST /buckets/1/admissions.json` joins the current person to the project with an ID of `1`, when the project's `admissions` policy lets them in: * `team` - any member of the account's team (not clients). * `employee` - team members who belong to the account's own company. - * `invite` - only the account owner. Everyone else has to be added with [Update who can access a project](#update-who-can-access-a-project). + * `invite` - only account owners. Everyone else has to be added with [Update who can access a project](#update-who-can-access-a-project). No parameters. No request body. This is the API equivalent of opening an all-access project in the web app and joining it yourself. It is **not** the same as [Update who can access a project](#update-who-can-access-a-project), which grants or revokes access for other people. -Returns `201 Created` with an empty body if the join succeeded. After a successful join, the project appears in [Get all projects](projects.md#get-all-projects) and [Get a project](projects.md#get-a-project) works for this person. +Returns `201 Created` with an empty body if the join succeeded. After a successful join, an active project appears in [Get all projects](projects.md#get-all-projects) and [Get a project](projects.md#get-a-project) works for this person. -If the project doesn't exist, or its policy doesn't let the current person in (for example, a client, or anyone other than the owner on an `invite` project), the API returns `404 Not Found`. If the person already has access, nothing changes and the response is a `302 Found` redirect to the project. +If the project doesn't exist, or its policy doesn't let the current person in, the API returns `404 Not Found`. The policy is checked first, so that includes people who already have access but aren't admitted by the policy, such as a client, or anyone other than an owner on an `invite` project. If the policy admits the person and they already have access, nothing changes and the response is a `302 Found` redirect to the project. Agent tokens get `403 Forbidden`. -`GET /projects.json` only lists projects the current person has already joined, so all-access projects they haven't joined yet don't appear there. Reading a resource inside such a project returns `403 Forbidden` with the URL to join: +`GET /projects.json` only lists projects the current person has already joined. Reading a project, or most resources inside one, that the person hasn't joined but could returns `403 Forbidden` with the URL to join: ```json { @@ -239,6 +239,8 @@ If the project doesn't exist, or its policy doesn't let the current person in (f } ``` +`admission_url` has no `.json` extension: POST to it with `Accept: application/json`, or add `.json`, to get `201 Created`. Without either, the join still happens but the response is a `302` redirect. Writes (`POST`, `PUT`, `DELETE`) inside an all-access project the person hasn't joined but could don't return `403`: they join the person to the project and then carry out the request. + ###### Copy as cURL ```shell diff --git a/sections/projects.md b/sections/projects.md index dd67ac9..c5fe041 100644 --- a/sections/projects.md +++ b/sections/projects.md @@ -738,9 +738,9 @@ _Optional parameters_: required. * `admissions` - specifies access policy for a project within the same account. Available options are: - * `invite` - only invited users can see the project. - * `employee` - anyone from the account can see the project. - * `team` - anyone from the account (except clients) can see the project. + * `invite` - only people added to the project have access. Account owners can also join it. + * `employee` - team members from the account's own company can join (not clients, and not team members from other companies). + * `team` - any team member, except clients, can join. ###### Example JSON Request From 62eaaada0ee781e78a530177630931f036218dbf Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 17 Sep 2026 07:09:39 +0200 Subject: [PATCH 4/5] Say most writes join, mark the seek-admission example, keep joins that fail later Bookmark writes don't go through the implicit join, so it's most writes, and a write that joins and is then refused keeps the join. The 403 example now names its route and carries the response heading and markers. --- sections/people.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sections/people.md b/sections/people.md index 9db4f3f..6e610c0 100644 --- a/sections/people.md +++ b/sections/people.md @@ -229,8 +229,10 @@ Returns `201 Created` with an empty body if the join succeeded. After a successf If the project doesn't exist, or its policy doesn't let the current person in, the API returns `404 Not Found`. The policy is checked first, so that includes people who already have access but aren't admitted by the policy, such as a client, or anyone other than an owner on an `invite` project. If the policy admits the person and they already have access, nothing changes and the response is a `302 Found` redirect to the project. Agent tokens get `403 Forbidden`. -`GET /projects.json` only lists projects the current person has already joined. Reading a project, or most resources inside one, that the person hasn't joined but could returns `403 Forbidden` with the URL to join: +`GET /projects.json` only lists projects the current person has already joined. Reading a project, or most resources inside one, that the person hasn't joined but could returns `403 Forbidden` with the URL to join. For example, `GET /projects/1.json` for such a project returns: +###### Example JSON Response + ```json { "message": "You must first seek admission", @@ -238,8 +240,9 @@ If the project doesn't exist, or its policy doesn't let the current person in, t "admission_method": "POST" } ``` + -`admission_url` has no `.json` extension: POST to it with `Accept: application/json`, or add `.json`, to get `201 Created`. Without either, the join still happens but the response is a `302` redirect. Writes (`POST`, `PUT`, `DELETE`) inside an all-access project the person hasn't joined but could don't return `403`: they join the person to the project and then carry out the request. +`admission_url` has no `.json` extension: POST to it with `Accept: application/json`, or add `.json`, to get `201 Created`. Without either, the join still happens but the response is a `302` redirect. Most writes (`POST`, `PUT`, `DELETE`) inside an all-access project the person hasn't joined but could don't return `403`: they join the person to the project and then carry out the request. The join stays even if the request itself is then refused. ###### Copy as cURL From 2a6d7fbc65ea0fe5f44add7c996a65cd0082cead Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Thu, 17 Sep 2026 07:21:43 +0200 Subject: [PATCH 5/5] Say where a joinable project's ID comes from GET /projects.json can't list it, so point at the project's web URL or at someone who can see the project. --- sections/people.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/people.md b/sections/people.md index 6e610c0..404b8be 100644 --- a/sections/people.md +++ b/sections/people.md @@ -229,7 +229,7 @@ Returns `201 Created` with an empty body if the join succeeded. After a successf If the project doesn't exist, or its policy doesn't let the current person in, the API returns `404 Not Found`. The policy is checked first, so that includes people who already have access but aren't admitted by the policy, such as a client, or anyone other than an owner on an `invite` project. If the policy admits the person and they already have access, nothing changes and the response is a `302 Found` redirect to the project. Agent tokens get `403 Forbidden`. -`GET /projects.json` only lists projects the current person has already joined. Reading a project, or most resources inside one, that the person hasn't joined but could returns `403 Forbidden` with the URL to join. For example, `GET /projects/1.json` for such a project returns: +`GET /projects.json` only lists projects the current person has already joined, so the ID of a project to join has to come from elsewhere: the project's web URL (`https://3.basecamp.com/$ACCOUNT_ID/projects/1`), or someone who can see the project. Reading a project, or most resources inside one, that the person hasn't joined but could returns `403 Forbidden` with the URL to join. For example, `GET /projects/1.json` for such a project returns: ###### Example JSON Response