mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-25 22:55:58 +00:00
feat: add the puter.teams SDK module
Team administration reaches the backend through the `/teams` routes
rather than a driver interface, because the gates it needs are route-level:
a user actor, a verified account, and a dual-window rate limit.
The module follows `apps/` and `perms/` in layout -- one file per method,
a thin `index.js`, a JSDoc-only `types.js`, and the `METHODS` rebinding so a
destructured method keeps its `this`. It does not follow `perms/lib/req.js`:
those endpoints resolve `{ error: true }` for backward compatibility, and
nothing here has callers to keep compatible, so this throws `PuterJSError`
with the backend's own code.
Every method takes a team `uid`. A handle is a mutable label that
deleting the team releases, so a stored handle can later resolve to a
different team.
The list methods offer the three forms `puter.apps.list()` does -- an array
by default, the page envelope under `cursor`/`includeTotal`, an async
iterator under `stream`. They refuse `offset`: these routes are keyset-only
and would otherwise return page one however far you asked to skip.
The surface covers the routes that exist today. Usage totals and member-email
correction have no backend route yet and are deliberately absent rather than
shipped as methods that 404. `deleteMember()` is here because the route it
needs lands in the commit below this one.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: Teams
|
||||
description: Administer a Puter team and the accounts it pays for with the Teams API
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
A team is an account that pays for other accounts. One owner account creates it, provisions member accounts, and can suspend or restore them. Members are ordinary Puter accounts — there are no roles to assign, and the team never gains access to a member's files.
|
||||
|
||||
`puter.teams` is the administrative surface for that. Every method takes a team `uid`.
|
||||
|
||||
```js
|
||||
const team = await puter.teams.create({ name: 'Acme', handle: 'acme' });
|
||||
await puter.teams.createMember(team.uid, { username: 'ann', email: 'ann@example.com' });
|
||||
const members = await puter.teams.listMembers(team.uid);
|
||||
```
|
||||
|
||||
## Availability
|
||||
|
||||
Teams are an opt-in deployment feature. Where they are turned off, the routes behind `puter.teams` do not exist and every method rejects with `not_found`.
|
||||
|
||||
`puter.teams.list()` is how an app tells the two apart: it rejects when the feature is off, and resolves to an empty array when it is on and the caller has no team.
|
||||
|
||||
```js
|
||||
let teams = [];
|
||||
try {
|
||||
teams = await puter.teams.list();
|
||||
} catch (e) {
|
||||
// Teams are unavailable here; show nothing.
|
||||
}
|
||||
```
|
||||
|
||||
## `uid`, not `handle`
|
||||
|
||||
A team has both a `uid` and an optional `handle`. Only the `uid` is stable.
|
||||
|
||||
A `handle` is a label: [`update()`](/Teams/update/) can change it, and deleting the team releases it for anyone else to take. A stored handle can therefore stop resolving — or, worse, start resolving to a different team. Display the `name` and `handle`; pass the `uid`.
|
||||
|
||||
## Methods
|
||||
|
||||
### Teams
|
||||
|
||||
| Method | Who can call it |
|
||||
| -- | -- |
|
||||
| [`create(options)`](/Teams/create/) | Any verified account |
|
||||
| [`list(options)`](/Teams/list/) | Any member, for their own teams |
|
||||
| [`get(uid)`](/Teams/get/) | Any member |
|
||||
| [`update(uid, attributes)`](/Teams/update/) | Owner account |
|
||||
| [`delete(uid)`](/Teams/delete/) | Owner account |
|
||||
|
||||
### Accounts
|
||||
|
||||
| Method | Who can call it |
|
||||
| -- | -- |
|
||||
| [`listMembers(uid, options)`](/Teams/listMembers/) | Any member |
|
||||
| [`createMember(uid, options)`](/Teams/createMember/) | Owner account |
|
||||
| [`resendActivation(uid, username)`](/Teams/resendActivation/) | Owner account |
|
||||
| [`disableMember(uid, username)`](/Teams/disableMember/) | Owner account |
|
||||
| [`enableMember(uid, username)`](/Teams/enableMember/) | Owner account |
|
||||
| [`resetPassword(uid, username)`](/Teams/resetPassword/) | Owner account |
|
||||
| [`deleteMemberAccount(uid, username)`](/Teams/deleteMemberAccount/) | Owner account |
|
||||
|
||||
### Audit
|
||||
|
||||
| Method | Who can call it |
|
||||
| -- | -- |
|
||||
| [`listAudit(uid, options)`](/Teams/listAudit/) | Owner account |
|
||||
| [`listOwnAudit(uid, options)`](/Teams/listOwnAudit/) | Any member, for their own entries |
|
||||
|
||||
## Pagination
|
||||
|
||||
`list()`, `listMembers()`, `listAudit()` and `listOwnAudit()` all take the same options and offer the same three forms:
|
||||
|
||||
| Call | Resolves to |
|
||||
| -- | -- |
|
||||
| No options | The whole set as an array, fetched page by page under the hood |
|
||||
| `{ cursor }` or `{ includeTotal: true }` | One `{ items, cursor? }` page. `cursor` is absent on the last page |
|
||||
| `{ stream: true }` | An async iterator of `{ items, cursor? }` pages |
|
||||
|
||||
`{ limit }` on its own still resolves to an array, capped at one page.
|
||||
|
||||
These routes are keyset-paginated, so `offset` is not accepted — passing it throws `invalid_request`. Pass `cursor` to resume from a position.
|
||||
|
||||
```js
|
||||
// Every member, however many pages it takes.
|
||||
const all = await puter.teams.listMembers(uid);
|
||||
|
||||
// One page at a time.
|
||||
let cursor = null;
|
||||
do {
|
||||
const page = await puter.teams.listMembers(uid, { limit: 50, cursor });
|
||||
cursor = page.cursor;
|
||||
} while (cursor);
|
||||
|
||||
// Or as a stream.
|
||||
for await (const page of puter.teams.listMembers(uid, { stream: true })) {
|
||||
console.log(page.items);
|
||||
}
|
||||
```
|
||||
|
||||
## Objects
|
||||
|
||||
#### `Team`
|
||||
|
||||
| Field | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| `uid` | `string` | The team's stable identifier. |
|
||||
| `name` | `string \| null` | Its display name. |
|
||||
| `handle` | `string \| null` | Its short handle, unique while it exists. |
|
||||
| `isOwner` | `boolean` | Whether the caller is the owner account. |
|
||||
| `createdAt` | `string` | When it was created. |
|
||||
|
||||
#### `TeamMember`
|
||||
|
||||
| Field | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| `username` | `string` | The member's Puter username. |
|
||||
| `orgOwned` | `boolean` | Whether the team provisioned and pays for this account. |
|
||||
| `createdAt` | `string` | When the account joined the team. |
|
||||
|
||||
#### `TeamAuditEntry`
|
||||
|
||||
| Field | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| `action` | `string` | What was done, e.g. `provision`, `disable`, `enable`, `delete_team`. |
|
||||
| `reason` | `string \| null` | The reason recorded with the action, when one was given. |
|
||||
| `username` | `string \| null` | The account it was about. |
|
||||
| `actorUsername` | `string \| null` | Who did it. `null` when Puter itself did. |
|
||||
| `createdAt` | `string` | When it happened. |
|
||||
|
||||
## Errors
|
||||
|
||||
Every method rejects with an `Error` carrying a stable `code`:
|
||||
|
||||
| Code | Meaning |
|
||||
| -- | -- |
|
||||
| `invalid_request` | The call was refused before reaching the server — a missing `name`, a blank `uid`, an `offset` on a keyset list. |
|
||||
| `bad_request` | The server refused the input, e.g. an invalid username or email. |
|
||||
| `unauthorized` | Not signed in, or signing in with an app or API token rather than a user session. |
|
||||
| `account_is_not_verified` | The caller's email has not been confirmed. Every `/teams` route requires it. |
|
||||
| `permission_denied` | Signed in, but not the owner account of this team. |
|
||||
| `not_found` | No such team, or teams are turned off on this deployment. |
|
||||
| `team_not_found` | No such team, or the caller is not a member of it. |
|
||||
| `not_an_org_account` | The named account is not a member of this team. |
|
||||
| `conflict` | The account has already been activated, so its credential cannot be reissued. |
|
||||
| `username_already_in_use` | The requested username is taken. The error carries `fields.suggestions` with free alternatives. |
|
||||
| `email_already_in_use` | The address already owns an account. |
|
||||
| `too_many_requests` | The rate limit was exceeded. See [Rate Limits & Quotas](/rate-limits-and-quotas/). |
|
||||
|
||||
## What is deliberately absent
|
||||
|
||||
- **No roles.** The owner account is the sole administrator; every other account is an ordinary member.
|
||||
- **No way to change a member's email or username.** After activation a member changes their own email, which re-verifies the holder. An administrator able to move the address could redirect a credential to themselves.
|
||||
- **No per-app usage breakdown.** Which applications a person uses is a fact about them, not about the bill.
|
||||
- **No sharing-policy controls.** A team cannot restrict who its members share with: there is no external-sharing policy, no domain allowlist, and no control over public links. A member shares exactly as any other Puter user does, with anyone. This is the assumption most teams bring the other way round, so it is worth stating plainly before you rely on it.
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: puter.teams.create()
|
||||
description: Create a team that pays for other Puter accounts.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Creates a team owned by the caller, who becomes its owner account.
|
||||
|
||||
The caller's email must be confirmed. Teams must be turned on for the deployment; where they are not, this rejects with `not_found`.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.create(options)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `options.name` (String) (required)
|
||||
|
||||
The team's display name.
|
||||
|
||||
#### `options.handle` (String | null) (optional)
|
||||
|
||||
A short handle, made of lowercase letters and digits separated by single hyphens, 3 to 64 characters. It must be free across the whole deployment, and a set of reserved words is refused. Omit it or pass `null` for none.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to a [`Team`](/Teams/#team).
|
||||
|
||||
Rejects with `invalid_request` if `name` is blank, `bad_request` if the handle is malformed or reserved, and `conflict` if the handle is taken.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Create a team</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const team = await puter.teams.create({
|
||||
name: 'Acme',
|
||||
handle: 'acme-' + Math.random().toString(36).slice(2, 8),
|
||||
});
|
||||
puter.print(`Created ${team.name} (${team.uid})`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: puter.teams.createMember()
|
||||
description: Provision a new Puter account owned by a team.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Provisions a new Puter account that the team owns and pays for. Owner account only.
|
||||
|
||||
There is no role to pick: every provisioned account is an ordinary member.
|
||||
|
||||
**The password comes back once.** It is not stored anywhere retrievable and there is no second chance to read it — deliver it to the member out of band. The member must change it at first sign-in. If it is lost before then, [`resendActivation()`](/Teams/resendActivation/) issues a fresh one.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.createMember(uid, options)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `options.username` (String) (required)
|
||||
|
||||
The username for the new account. Usernames come from the same pool as ordinary sign-ups, so it must be free across the whole of Puter.
|
||||
|
||||
#### `options.email` (String) (required)
|
||||
|
||||
The address the member is reachable at. It must not already own an account. The address came from the administrator rather than its holder, so the account is created needing email confirmation.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to `{ username, temporaryPassword }`.
|
||||
|
||||
Rejects with `username_already_in_use` — with free alternatives in `fields.suggestions` — or `email_already_in_use`.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Add an account to a team</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const name = 'member' + Math.random().toString(36).slice(2, 8);
|
||||
const account = await puter.teams.createMember(team.uid, {
|
||||
username: name,
|
||||
email: `${name}@example.com`,
|
||||
});
|
||||
// Shown once; hand it over out of band.
|
||||
puter.print(`${account.username}: ${account.temporaryPassword}`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: puter.teams.delete()
|
||||
description: Delete a team. The accounts it paid for keep existing.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Deletes a team. Owner account only.
|
||||
|
||||
**The accounts the team provisioned are not deleted.** What stops is the team paying for them, so the per-account charges end and the storage charges do not. Its handle is released, and its [audit log](/Teams/listAudit/) stays readable to the owner account afterwards.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.delete(uid)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to nothing once the team is gone.
|
||||
|
||||
Rejects with `not_the_team_owner` if the caller is not the owner account.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Create a team then delete it</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const team = await puter.teams.create({ name: 'Temporary' });
|
||||
puter.print(`Created ${team.uid}<br>`);
|
||||
await puter.teams.delete(team.uid);
|
||||
puter.print('Deleted. Any accounts it provisioned still exist.');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: puter.teams.deleteMemberAccount()
|
||||
description: Permanently remove an account a team owns.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Permanently removes an account the team owns. Its files are deleted, its username returns to the pool, and every credential is invalidated. Owner account only.
|
||||
|
||||
**This is irreversible, and there is no restore window.** The account must already be suspended with [`disableMember()`](/Teams/disableMember/) — a live account is refused. That ordering is deliberate: it puts a reversible step in front of the only irreversible operation in the API, so nothing here deletes a working account in a single call.
|
||||
|
||||
Disabling already stopped the per-account charge. This is what stops the charge for the bytes the account held, and it is the only thing that does. Nothing removes a suspended account on a timer — it persists, costing only its storage, until you ask for it to go.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.deleteMemberAccount(uid, username)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `username` (String) (required)
|
||||
|
||||
The member's username. It must be an account this team provisioned, and it must already be suspended.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to nothing once the account is gone.
|
||||
|
||||
Rejects with `account_must_be_disabled_first` if the account is still live, `not_an_org_account` if it does not belong to this team, and `not_the_team_owner` if the caller is not the owner account.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Suspend, then remove for good</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const name = 'member' + Math.random().toString(36).slice(2, 8);
|
||||
await puter.teams.createMember(team.uid, { username: name, email: `${name}@example.com` });
|
||||
|
||||
// Deleting straight away is refused — disable is always the step before.
|
||||
try {
|
||||
await puter.teams.deleteMemberAccount(team.uid, name);
|
||||
} catch (e) {
|
||||
puter.print(`Refused: ${e.code}<br>`);
|
||||
}
|
||||
|
||||
await puter.teams.disableMember(team.uid, name);
|
||||
await puter.teams.deleteMemberAccount(team.uid, name);
|
||||
puter.print(`${name} deleted`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: puter.teams.disableMember()
|
||||
description: Suspend an account a team owns.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Suspends an account the team owns, ending its sessions. Owner account only, and reversible with [`enableMember()`](/Teams/enableMember/).
|
||||
|
||||
**A disabled account still costs the team money.** The per-account charge stops; the charge for the bytes it holds does not. To stop that, its files have to go.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.disableMember(uid, username)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `username` (String) (required)
|
||||
|
||||
The member's username. It must be an account this team provisioned — a pre-existing account that joined cannot be suspended by the team.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to nothing once the account is suspended.
|
||||
|
||||
Rejects with `not_an_org_account` if the account does not belong to this team, and `not_the_team_owner` if the caller is not the owner account.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Suspend and restore an account</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const name = 'member' + Math.random().toString(36).slice(2, 8);
|
||||
await puter.teams.createMember(team.uid, { username: name, email: `${name}@example.com` });
|
||||
|
||||
await puter.teams.disableMember(team.uid, name);
|
||||
puter.print(`${name} suspended<br>`);
|
||||
|
||||
await puter.teams.enableMember(team.uid, name);
|
||||
puter.print(`${name} restored`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: puter.teams.enableMember()
|
||||
description: Restore an account previously suspended.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Restores an account previously suspended with [`disableMember()`](/Teams/disableMember/). Owner account only. The account can sign in again and the team resumes paying its per-account charge.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.enableMember(uid, username)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `username` (String) (required)
|
||||
|
||||
The member's username.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to nothing once the account is restored.
|
||||
|
||||
Rejects with `not_an_org_account` if the account does not belong to this team, and `not_the_team_owner` if the caller is not the owner account.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Restore a suspended account</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const [member] = (await puter.teams.listMembers(team.uid)).filter(m => m.orgOwned);
|
||||
if (!member) return puter.print('No provisioned account.');
|
||||
await puter.teams.enableMember(team.uid, member.username);
|
||||
puter.print(`${member.username} can sign in again`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: puter.teams.get()
|
||||
description: Get one team by its uid.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Returns one team the caller belongs to.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.get(uid)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier, as returned by [`create()`](/Teams/create/) or [`list()`](/Teams/list/). Handles are not accepted — see [`uid`, not `handle`](/Teams/#uid-not-handle).
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to a [`Team`](/Teams/#team).
|
||||
|
||||
Rejects with `team_not_found` if the team does not exist or the caller is not a member. The two are not distinguished, so this cannot be used to probe for teams the caller has nothing to do with.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Read a team back</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [first] = await puter.teams.list();
|
||||
if (!first) return puter.print('No team.');
|
||||
const team = await puter.teams.get(first.uid);
|
||||
puter.print(`${team.name} (@${team.handle ?? 'no handle'})`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: puter.teams.list()
|
||||
description: List the teams you belong to.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Returns the teams the caller belongs to — both those they own and those they were provisioned into.
|
||||
|
||||
This is also how an app discovers whether teams exist on this deployment at all: it rejects with `not_found` where the feature is off, and resolves to an empty array where it is on and the caller has no team.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.list()
|
||||
puter.teams.list(options)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `options` (Object) (optional)
|
||||
|
||||
The standard list options — `limit`, `cursor`, `includeTotal` and `stream`. See [Pagination](/Teams/#pagination) for what each form returns. `offset` is not accepted.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to an array of [`Team`](/Teams/#team) objects, or to a `{ items, cursor? }` page when a pagination option is given. With `stream: true` it returns an async iterator of pages instead.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Show the caller's teams, or nothing where the feature is off</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
let teams = [];
|
||||
try {
|
||||
teams = await puter.teams.list();
|
||||
} catch (e) {
|
||||
puter.print('Teams are not available here.');
|
||||
return;
|
||||
}
|
||||
if (teams.length === 0) {
|
||||
puter.print('You are not in a team.');
|
||||
return;
|
||||
}
|
||||
for (const team of teams) {
|
||||
puter.print(`${team.name} - ${team.isOwner ? 'owner' : 'member'}<br>`);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: puter.teams.listAudit()
|
||||
description: Read a team's record of what it did to its accounts.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Returns everything the team has done to its accounts, newest first. Owner account only.
|
||||
|
||||
The log is insert-only and survives the team: after [`delete()`](/Teams/delete/) the owner account can still read it, which is the point of keeping it.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.listAudit(uid)
|
||||
puter.teams.listAudit(uid, options)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `options` (Object) (optional)
|
||||
|
||||
The standard list options — `limit`, `cursor`, `includeTotal` and `stream`. See [Pagination](/Teams/#pagination). `offset` is not accepted.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to an array of [`TeamAuditEntry`](/Teams/#teamauditentry) objects, or to a `{ items, cursor? }` page when a pagination option is given. With `stream: true` it returns an async iterator of pages instead.
|
||||
|
||||
Rejects with `not_the_team_owner` if the caller is a member rather than the owner account. Members read their own entries with [`listOwnAudit()`](/Teams/listOwnAudit/).
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Show what a team has done</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
for (const entry of await puter.teams.listAudit(team.uid)) {
|
||||
puter.print(`${entry.createdAt} ${entry.actorUsername} ${entry.action} ${entry.username}<br>`);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: puter.teams.listMembers()
|
||||
description: List the accounts belonging to a team.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Returns the accounts belonging to a team, including the owner account.
|
||||
|
||||
Any member may call it. The response carries no email address, activation state or usage — those stay on the administrative methods.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.listMembers(uid)
|
||||
puter.teams.listMembers(uid, options)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `options` (Object) (optional)
|
||||
|
||||
The standard list options — `limit`, `cursor`, `includeTotal` and `stream`. See [Pagination](/Teams/#pagination). `offset` is not accepted.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to an array of [`TeamMember`](/Teams/#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.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">List everyone in the first team</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
for (const member of await puter.teams.listMembers(team.uid)) {
|
||||
puter.print(`${member.username}${member.orgOwned ? ' (provisioned)' : ''}<br>`);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: puter.teams.listOwnAudit()
|
||||
description: Read what a team did to your own account.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Returns the caller's own entries in a team's audit log — what the team did to their account, and who did it. Any member may call it.
|
||||
|
||||
It exists so that being administered is not something that happens invisibly. It shows only the caller's entries; the whole log is [`listAudit()`](/Teams/listAudit/), which is owner-account only.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.listOwnAudit(uid)
|
||||
puter.teams.listOwnAudit(uid, options)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `options` (Object) (optional)
|
||||
|
||||
The standard list options — `limit`, `cursor`, `includeTotal` and `stream`. See [Pagination](/Teams/#pagination). `offset` is not accepted.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to an array of [`TeamAuditEntry`](/Teams/#teamauditentry) objects, or to a `{ items, cursor? }` page when a pagination option is given. With `stream: true` it returns an async iterator of pages instead.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Show what was done to your own account</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('Not in a team.');
|
||||
const entries = await puter.teams.listOwnAudit(team.uid);
|
||||
if (entries.length === 0) return puter.print('Nothing has been done to your account.');
|
||||
for (const entry of entries) {
|
||||
puter.print(`${entry.createdAt} - ${entry.action} by ${entry.actorUsername}<br>`);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: puter.teams.resendActivation()
|
||||
description: Issue a fresh one-time credential for an account that has never signed in.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Issues a fresh one-time credential for an account that has never signed in, invalidating the previous one. Owner account only. Use it when the password from [`createMember()`](/Teams/createMember/) was lost before the member used it.
|
||||
|
||||
**It refuses once the account has been activated**, rejecting with `conflict`. After activation the member owns their own password, and an administrator able to replace it would be able to reach their files. An activated member resets their own password through the normal Puter flow.
|
||||
|
||||
The credential comes back once and is not retrievable afterwards. The member is emailed a notice that the account was set up; the notice carries no credential.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.resendActivation(uid, username)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `username` (String) (required)
|
||||
|
||||
The member's username. It must be an account this team provisioned.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to `{ username, temporaryPassword }`.
|
||||
|
||||
Rejects with `conflict` if the account has already been activated, and `not_an_org_account` if it does not belong to this team.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Reissue a credential, and see it refused after activation</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const members = await puter.teams.listMembers(team.uid);
|
||||
const target = members.find(m => m.orgOwned);
|
||||
if (!target) return puter.print('No provisioned account.');
|
||||
try {
|
||||
const again = await puter.teams.resendActivation(team.uid, target.username);
|
||||
puter.print(`New credential for ${again.username}: ${again.temporaryPassword}`);
|
||||
} catch (e) {
|
||||
if (e.code === 'conflict') puter.print('Already activated - they reset it themselves.');
|
||||
else throw e;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: puter.teams.resetPassword()
|
||||
description: Issue a new temporary password for an account a team owns.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Issues a new temporary password for an account the team owns and ends its sessions. Owner account only.
|
||||
|
||||
Unlike [`resendActivation()`](/Teams/resendActivation/), which only works before an account has ever been used, this works on a live account. **That makes it the one route from a team to a member's data**, so it is bounded in two ways that cannot be turned off: an audit row is written, and the member is emailed. Both happen on every call.
|
||||
|
||||
Two-factor authentication is left alone. A team can replace a member's password and cannot clear their second factor.
|
||||
|
||||
The temporary password is returned **once** and is not retrievable afterwards — deliver it out of band. It stops working 24 hours after it is issued, so an unused reset expires rather than becoming a standing credential. Until the member chooses their own password, they can sign in and do nothing else: every other request fails with `password_change_required`.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.resetPassword(uid, username)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `username` (String) (required)
|
||||
|
||||
The member's username. It must be an account this team provisioned.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to an object with:
|
||||
|
||||
#### `username` (String)
|
||||
|
||||
The account the credential belongs to.
|
||||
|
||||
#### `temporaryPassword` (String)
|
||||
|
||||
The new password. Shown once — this response is the only place it appears.
|
||||
|
||||
Rejects with `not_an_org_account` if the account does not belong to this team, and `not_the_team_owner` if the caller is not the owner account.
|
||||
|
||||
## Rate limit
|
||||
|
||||
20 per day, separate from the general administrative budget. See [Rate Limits and Quotas](/rate-limits-and-quotas/).
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Reset a member's password</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const members = await puter.teams.listMembers(team.uid);
|
||||
const member = members.find(m => m.orgOwned);
|
||||
if (!member) return puter.print('No member accounts.');
|
||||
|
||||
const { temporaryPassword } = await puter.teams.resetPassword(team.uid, member.username);
|
||||
puter.print(`New password for ${member.username}: ${temporaryPassword}<br>`);
|
||||
puter.print('Deliver this out of band — it expires in 24 hours.');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: puter.teams.update()
|
||||
description: Rename a team or change its handle.
|
||||
platforms: [websites, apps]
|
||||
---
|
||||
|
||||
<div class="info">The Teams API is in beta. Method shapes, limits, and behavior may change between releases.</div>
|
||||
|
||||
Renames a team or changes its handle. Owner account only.
|
||||
|
||||
Changing a handle frees the old one for anyone else to claim, so nothing should store a handle as a reference to a team.
|
||||
|
||||
## Syntax
|
||||
|
||||
```js
|
||||
puter.teams.update(uid, attributes)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
#### `uid` (String) (required)
|
||||
|
||||
The team's identifier.
|
||||
|
||||
#### `attributes.name` (String) (optional)
|
||||
|
||||
A new display name.
|
||||
|
||||
#### `attributes.handle` (String | null) (optional)
|
||||
|
||||
A new handle, or `null` to release the current one. Omitting the field leaves the handle alone; that is different from passing `null`.
|
||||
|
||||
## Return value
|
||||
|
||||
A `Promise` that resolves to the updated [`Team`](/Teams/#team).
|
||||
|
||||
Rejects with `not_the_team_owner` if the caller is a member rather than the owner account, and `conflict` if the handle is taken.
|
||||
|
||||
## Examples
|
||||
|
||||
<strong class="example-title">Rename a team and release its handle</strong>
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<script src="https://js.puter.com/v2/"></script>
|
||||
<script>
|
||||
(async () => {
|
||||
const [team] = await puter.teams.list();
|
||||
if (!team) return puter.print('No team.');
|
||||
const renamed = await puter.teams.update(team.uid, { name: 'Acme Inc' });
|
||||
puter.print(`Now called ${renamed.name}<br>`);
|
||||
const released = await puter.teams.update(team.uid, { handle: null });
|
||||
puter.print(`Handle is now ${released.handle}`);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -206,6 +206,10 @@ Lowering the seat limit never disables anyone. A team already above a reduced li
|
||||
|
||||
Both limits are per deployment (`max_teams_per_user`, `max_seats_per_team`) rather than per team, so raising them moves every team at once.
|
||||
|
||||
A team's whole configuration is its name, its handle, and whether its directory is open to apps. In particular there is **no sharing policy**: a team cannot restrict who its members share with, by domain or otherwise, and there is no control over public links. Members share exactly as any other Puter account does.
|
||||
|
||||
Both buckets are per account, not per team, so administering several teams spends one budget, and the read limit is one bucket shared by every listing call. Where a deployment has teams off, `puter.teams` rejects with `not_found` rather than `too_many_requests`.
|
||||
|
||||
### Events
|
||||
|
||||
One write can reach many subscriptions, so events are bounded on both halves: how much you may register, and how much any one event may turn into.
|
||||
|
||||
@@ -1119,6 +1119,127 @@ let sidebar = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Teams',
|
||||
title_tag: 'Teams',
|
||||
icon: '/assets/img/auth.svg',
|
||||
source: '/Teams.md',
|
||||
path: '/Teams',
|
||||
children: [
|
||||
{
|
||||
title: '<code>create()</code>',
|
||||
page_title: '<code>puter.teams.create()</code>',
|
||||
title_tag: 'puter.teams.create()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/create.md',
|
||||
path: '/Teams/create',
|
||||
},
|
||||
{
|
||||
title: '<code>list()</code>',
|
||||
page_title: '<code>puter.teams.list()</code>',
|
||||
title_tag: 'puter.teams.list()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/list.md',
|
||||
path: '/Teams/list',
|
||||
},
|
||||
{
|
||||
title: '<code>get()</code>',
|
||||
page_title: '<code>puter.teams.get()</code>',
|
||||
title_tag: 'puter.teams.get()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/get.md',
|
||||
path: '/Teams/get',
|
||||
},
|
||||
{
|
||||
title: '<code>update()</code>',
|
||||
page_title: '<code>puter.teams.update()</code>',
|
||||
title_tag: 'puter.teams.update()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/update.md',
|
||||
path: '/Teams/update',
|
||||
},
|
||||
{
|
||||
title: '<code>delete()</code>',
|
||||
page_title: '<code>puter.teams.delete()</code>',
|
||||
title_tag: 'puter.teams.delete()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/delete.md',
|
||||
path: '/Teams/delete',
|
||||
},
|
||||
{
|
||||
title: '<code>listMembers()</code>',
|
||||
page_title: '<code>puter.teams.listMembers()</code>',
|
||||
title_tag: 'puter.teams.listMembers()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/listMembers.md',
|
||||
path: '/Teams/listMembers',
|
||||
},
|
||||
{
|
||||
title: '<code>createMember()</code>',
|
||||
page_title: '<code>puter.teams.createMember()</code>',
|
||||
title_tag: 'puter.teams.createMember()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/createMember.md',
|
||||
path: '/Teams/createMember',
|
||||
},
|
||||
{
|
||||
title: '<code>resendActivation()</code>',
|
||||
page_title: '<code>puter.teams.resendActivation()</code>',
|
||||
title_tag: 'puter.teams.resendActivation()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/resendActivation.md',
|
||||
path: '/Teams/resendActivation',
|
||||
},
|
||||
{
|
||||
title: '<code>disableMember()</code>',
|
||||
page_title: '<code>puter.teams.disableMember()</code>',
|
||||
title_tag: 'puter.teams.disableMember()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/disableMember.md',
|
||||
path: '/Teams/disableMember',
|
||||
},
|
||||
{
|
||||
title: '<code>enableMember()</code>',
|
||||
page_title: '<code>puter.teams.enableMember()</code>',
|
||||
title_tag: 'puter.teams.enableMember()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/enableMember.md',
|
||||
path: '/Teams/enableMember',
|
||||
},
|
||||
{
|
||||
title: '<code>resetPassword()</code>',
|
||||
page_title: '<code>puter.teams.resetPassword()</code>',
|
||||
title_tag: 'puter.teams.resetPassword()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/resetPassword.md',
|
||||
path: '/Teams/resetPassword',
|
||||
},
|
||||
{
|
||||
title: '<code>deleteMemberAccount()</code>',
|
||||
page_title: '<code>puter.teams.deleteMemberAccount()</code>',
|
||||
title_tag: 'puter.teams.deleteMemberAccount()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/deleteMemberAccount.md',
|
||||
path: '/Teams/deleteMemberAccount',
|
||||
},
|
||||
{
|
||||
title: '<code>listAudit()</code>',
|
||||
page_title: '<code>puter.teams.listAudit()</code>',
|
||||
title_tag: 'puter.teams.listAudit()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/listAudit.md',
|
||||
path: '/Teams/listAudit',
|
||||
},
|
||||
{
|
||||
title: '<code>listOwnAudit()</code>',
|
||||
page_title: '<code>puter.teams.listOwnAudit()</code>',
|
||||
title_tag: 'puter.teams.listOwnAudit()',
|
||||
icon: '/assets/img/function.svg',
|
||||
source: '/Teams/listOwnAudit.md',
|
||||
path: '/Teams/listOwnAudit',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Utilities',
|
||||
title_tag: 'Utilities',
|
||||
|
||||
Reference in New Issue
Block a user