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.
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);
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.
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() 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.
| Method | Who can call it |
|---|---|
create(options) |
Any verified account |
list(options) |
Any member, for their own teams |
get(uid) |
Any member |
update(uid, attributes) |
Owner account |
delete(uid) |
Owner account |
| Method | Who can call it |
|---|---|
listMembers(uid, options) |
Any member |
createMember(uid, options) |
Owner account |
resendActivation(uid, username) |
Owner account |
disableMember(uid, username) |
Owner account |
enableMember(uid, username) |
Owner account |
resetPassword(uid, username) |
Owner account |
deleteMemberAccount(uid, username) |
Owner account |
| Method | Who can call it |
|---|---|
listAudit(uid, options) |
Owner account |
listOwnAudit(uid, options) |
Any member, for their own entries |
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.
// 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);
}
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. |
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. |