From 3d1357455ff9a84e00dfa0aaf7924ba2fa2d888c Mon Sep 17 00:00:00 2001 From: Juan Fernando Castro Date: Wed, 30 Sep 2026 13:11:42 -0400 Subject: [PATCH] docs: teams recipe and playground examples (PUT-1966) (#3967) * docs: add the easy teams recipe and playground examples Covers the read-only surface an app actually has: list(), listMembers() and listDirectory(). Adds the teams recipe tag the build validates against, and a Teams section to the playground. Each example handles the two states that are easy to confuse: teams turned off on the deployment (not_found) and a team that has not opened its directory to apps (team_not_found). * docs: open the teams recipe with what it lets an app build * refine --------- Co-authored-by: Reynaldi Chernando --- src/docs/src/Teams.md | 5 +- src/docs/src/Teams/listDirectory.md | 3 +- src/docs/src/Teams/listMembers.md | 102 ---------------- src/docs/src/examples.js | 17 +++ .../playground/examples/teams-directory.html | 48 ++++++++ .../src/playground/examples/teams-list.html | 31 +++++ src/docs/src/recipe-tags.js | 1 + src/docs/src/recipes/add-team-support.md | 113 ++++++++++++++++++ src/docs/src/sidebar.js | 8 -- 9 files changed, 213 insertions(+), 115 deletions(-) delete mode 100644 src/docs/src/Teams/listMembers.md create mode 100644 src/docs/src/playground/examples/teams-directory.html create mode 100644 src/docs/src/playground/examples/teams-list.html create mode 100644 src/docs/src/recipes/add-team-support.md diff --git a/src/docs/src/Teams.md b/src/docs/src/Teams.md index 878453ca7..8e06af99e 100644 --- a/src/docs/src/Teams.md +++ b/src/docs/src/Teams.md @@ -14,7 +14,7 @@ A team is a Puter account that pays for other accounts. Members are ordinary Put **Team context.** `list()` tells you whether the signed-in user belongs to a team, and is also how you detect whether the deployment has Teams at all: it rejects with `not_found` where the feature is off, and resolves to an empty array where it is on and the user has no team. Called from an app, it only includes teams whose owner opened the directory to apps (see below); others are simply omitted. -**Member and colleague lookup.** `listDirectory()` returns the team's active members so your app can suggest people by name instead of asking users to type usernames. `listMembers()` returns the same roster; called with the user's own session or API token it also carries `orgOwned` and `createdAt`, but an app only ever gets `username`. Both are opt-in per team: until an owner opens the directory to apps, an app gets `team_not_found`, which is indistinguishable from having no team. +**Member and colleague lookup.** `listDirectory()` returns the team's active members so your app can suggest people by name instead of asking users to type usernames. It is opt-in per team: until an owner opens the directory to apps, an app gets `team_not_found`, which is indistinguishable from having no team. **Sharing with a team.** Anything shared with a team reaches every member with one grant, including anyone added later. Pass the team's `uid` as the recipient of [`puter.fs.share()`](/FS/share/); there is no string form, since a bare string is always read as an email or username. @@ -23,10 +23,9 @@ A team is a Puter account that pays for other accounts. Members are ordinary Put ## Functions - **[`puter.teams.list()`](/Teams/list/)** - List the teams the signed-in user belongs to, and detect whether Teams is available at all -- **[`puter.teams.listMembers()`](/Teams/listMembers/)** - List a team's accounts, with owner-only seat details - **[`puter.teams.listDirectory()`](/Teams/listDirectory/)** - List a team's members, where the owner has opened the directory to apps -All three are keyset-paginated and take the same options; see each method page for the paging forms and the full error list. +Both are keyset-paginated and take the same options; see each method page for the paging forms and the full error list. ## Examples diff --git a/src/docs/src/Teams/listDirectory.md b/src/docs/src/Teams/listDirectory.md index e34df1ea0..6888ceb3c 100644 --- a/src/docs/src/Teams/listDirectory.md +++ b/src/docs/src/Teams/listDirectory.md @@ -10,8 +10,7 @@ Returns the team's member directory — the colleagues of the user your app is running for, active accounts only. It is consent-gated: the team's owner has to open the directory to apps, and until they do it rejects with `team_not_found` for every caller, indistinguishable from the team not existing. The same -consent decides whether an app sees the team in [`list()`](/Teams/list/) and -may call [`listMembers()`](/Teams/listMembers/). +consent decides whether an app sees the team in [`list()`](/Teams/list/). The membership is always the signed-in user's, never the app's: an app can only see the directory of a team its user belongs to. diff --git a/src/docs/src/Teams/listMembers.md b/src/docs/src/Teams/listMembers.md deleted file mode 100644 index d6c751f02..000000000 --- a/src/docs/src/Teams/listMembers.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: puter.teams.listMembers() -description: List a team's accounts. -platforms: [websites, apps, nodejs, workers] ---- - -
The Teams API is in beta. Method shapes, limits, and behavior may change between releases.
- -Returns a team's accounts, owner included, whether provisioned by the team or -joined from an existing account. - -Called from an app, it needs the same consent as -[`listDirectory()`](/Teams/listDirectory/): until the team's owner opens the -directory to apps it rejects with `team_not_found`, indistinguishable from the -team not existing. An app also sees only active accounts: suspended seats and -seats that have not activated yet are left out, and each `TeamMember` carries -only `username` — no `orgOwned`, `createdAt`, or `uuid`. Called with the -user's own session or API token, it lists every account whatever the -directory setting, with the full `TeamMember` shape. - -The membership is always the signed-in user's, never the app's: an app can -only see the members of a team its user belongs to. - -## Syntax - -```js -puter.teams.listMembers(uid) -puter.teams.listMembers(uid, options) -``` - -## Parameters - -#### `uid` (String) (required) - -The team's `uid`, from [`list()`](/Teams/list/). - -#### `options` (Object) (optional) - -The standard list options. All four are optional, and they decide the shape of what resolves: - -| Call | Resolves to | -| -- | -- | -| No options | The whole set as an array, fetched page by page under the hood | -| `{ limit }` | An array, capped at one page | -| `{ cursor }` or `{ includeTotal: true }` | One `{ items, cursor? }` page. `cursor` is absent on the last page | -| `{ stream: true }` | An async iterator of `{ items, cursor? }` pages | - -This route is keyset-paginated, so `offset` is not accepted — passing it throws `invalid_request`. Pass `cursor` to resume from a position. - -## Return value - -A `Promise` that resolves to an array of -`TeamMember` objects, or to a -`{ items, cursor? }` page when a pagination option is given. With -`stream: true` it returns an async iterator of pages instead. - -#### `TeamMember` - -| Field | Type | Description | -| -- | -- | -- | -| `username` | `string` | The member's Puter username. | -| `orgOwned` | `boolean` (optional) | Whether the team provisioned and pays for this account, as opposed to a pre-existing account that joined it. Absent when an app calls: it gets `username` only. | -| `createdAt` | `string` (optional) | When the account joined the team. Absent when an app calls. | -| `uuid` | `string` (optional) | The account's stable identifier. Present only when the team owner calls with their own session or API token. | - -## Errors - -A rejection carries an `Error` with a stable `code`: - -| Code | Meaning | -| -- | -- | -| `invalid_request` | Refused before reaching the server — a blank `uid`, or an `offset` on a keyset list. | -| `token_missing` | No authentication token was presented. | -| `token_auth_failed` | The token presented did not authenticate. | -| `forbidden` | Called with a scoped access token. | -| `account_is_not_verified` | The caller's email has not been confirmed. | -| `not_found` | Teams are turned off on this deployment. | -| `team_not_found` | No such team, the caller is not a member of it, or — for an app — the owner has not opened the directory to apps. | -| `too_many_requests` | The rate limit was exceeded. See [Rate Limits & Quotas](/rate-limits-and-quotas/). | - -## Examples - -List a team's accounts - -```html - - - - - - -``` diff --git a/src/docs/src/examples.js b/src/docs/src/examples.js index 112990cfe..1ff2e27ec 100644 --- a/src/docs/src/examples.js +++ b/src/docs/src/examples.js @@ -953,6 +953,23 @@ const examples = [ }, ], }, + { + title: 'Teams', + children: [ + { + title: "Find the user's team", + description: "Check whether the signed-in user belongs to a team with Puter.js teams API. Run and experiment with this example directly in the playground.", + slug: 'teams-list', + source: '/playground/examples/teams-list.html', + }, + { + title: 'Offer colleagues in a picker', + description: 'Build a colleague picker from a team directory with Puter.js teams API. Run and experiment with this example in the playground.', + slug: 'teams-directory', + source: '/playground/examples/teams-directory.html', + }, + ], + }, { title: 'Workers', children: [ diff --git a/src/docs/src/playground/examples/teams-directory.html b/src/docs/src/playground/examples/teams-directory.html new file mode 100644 index 000000000..8d84285d6 --- /dev/null +++ b/src/docs/src/playground/examples/teams-directory.html @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/src/docs/src/playground/examples/teams-list.html b/src/docs/src/playground/examples/teams-list.html new file mode 100644 index 000000000..a4df777ec --- /dev/null +++ b/src/docs/src/playground/examples/teams-list.html @@ -0,0 +1,31 @@ + + + + + + diff --git a/src/docs/src/recipe-tags.js b/src/docs/src/recipe-tags.js index 97b68e742..0597ab551 100644 --- a/src/docs/src/recipe-tags.js +++ b/src/docs/src/recipe-tags.js @@ -13,6 +13,7 @@ const recipeTags = { hosting: 'Hosting', workers: 'Workers', ui: 'UI', + teams: 'Teams', performance: 'Performance', 'data-modeling': 'Data Modeling', }; diff --git a/src/docs/src/recipes/add-team-support.md b/src/docs/src/recipes/add-team-support.md new file mode 100644 index 000000000..95bbad257 --- /dev/null +++ b/src/docs/src/recipes/add-team-support.md @@ -0,0 +1,113 @@ +--- +title: Add Team Support +description: "Learn how to add team support with Puter.js, so your app can get the user's team and its members." +tags: [teams, auth] +order: 60 +--- + +
The Teams API is in beta. Method shapes, limits, and behavior may change between releases.
+ +On Puter, a user can create a team and add accounts to it. The user who creates +the team becomes its owner. When someone on a +team signs in to your app, you can read which team they belong to and who else +is on it. This lets your app greet them with their team's name and suggest +colleagues in places like an assignee dropdown or a share dialog, instead of +asking for a typed username. + +## Check whether the user is on a team + +To get the teams the signed-in user belongs to, call +[`list()`](/Teams/list/). An empty array means the user is not on a team: + +```js +const teams = await puter.teams.list(); + +if (teams.length === 0) { + console.log('Not on a team'); +} else { + console.log(`On ${teams.length} team(s)`); +} +``` + +## Show which team + +Each team has a `name` and a `handle`, and either one may be `null`: + +```js +const [team] = await puter.teams.list(); + +console.log(team.name ?? team.handle ?? 'Unnamed team'); +console.log(team.createdAt); // '2026-09-28T10:04:00Z' +console.log(team.isOwner); // true if this user owns the team +``` + +To refer to a team, use `team.uid`. The +[`listDirectory()`](/Teams/listDirectory/) method takes it, and it is the value +to store. The owner can rename the `handle`, and a deleted team's +handle becomes free for another team to take, so only `uid` always points to +the same team. + +## List the users in a team + +To get the active users in a team, call +[`listDirectory()`](/Teams/listDirectory/) with the team's `uid`. Each entry has a `username` and a `uuid`. The `uuid` +stays the same when the user changes their username, so store the `uuid` if +you save a reference to someone: + +```js +const entries = await puter.teams.listDirectory(team.uid); + +for (const { username, uuid } of entries) { + console.log(username, uuid); +} +``` + +With this list, your app can suggest team members wherever the user picks +people. For example, when the user [shares a file](/recipes/share-a-file/), +your app can suggest usernames from their team, so the user does not have to +type a username from memory. + +## Check that the team has turned on its directory + +Your app can see a team only after the team owner turns on its directory. It is +off by default. Until the owner turns it on, [`list()`](/Teams/list/) leaves +the team out of its result, and +[`listDirectory()`](/Teams/listDirectory/) rejects with `team_not_found`. The +same calls made from the user's own Puter session still return the team. + +Each team has a `directoryEnabled` flag. Check it before listing the users in a team, so +you can tell the user what to do: + +```js +const [team] = await puter.teams.list(); + +if (team.directoryEnabled) { + const entries = await puter.teams.listDirectory(team.uid); + console.log(entries); +} else { + console.log('Ask the team owner to turn on the directory.'); +} +``` + +## List a large team + +By default, [`listDirectory()`](/Teams/listDirectory/) returns the whole list +as an array. For a large team, pass `stream: true` to read it one page at a +time. Each page has an `items` array: + +```js +for await (const page of puter.teams.listDirectory(team.uid, { stream: true })) { + for (const { username } of page.items) console.log(username); +} +``` + +To load one page at a time yourself, such as behind a "Load more" button, pass +`limit` and `cursor`. The result has a `cursor` for the next page while more +pages remain: + +```js +const page = await puter.teams.listDirectory(team.uid, { limit: 50, cursor: null }); + +console.log(page.items); +console.log(page.cursor); +``` diff --git a/src/docs/src/sidebar.js b/src/docs/src/sidebar.js index d16cb372c..2e63b6bdf 100755 --- a/src/docs/src/sidebar.js +++ b/src/docs/src/sidebar.js @@ -1199,14 +1199,6 @@ let sidebar = [ source: '/Teams/list.md', path: '/Teams/list', }, - { - title: 'listMembers()', - page_title: 'puter.teams.listMembers()', - title_tag: 'puter.teams.listMembers()', - icon: '/assets/img/function.svg', - source: '/Teams/listMembers.md', - path: '/Teams/listMembers', - }, { title: 'listDirectory()', page_title: 'puter.teams.listDirectory()',