Teams
Teams are named subgroups inside an org. They give you finer-grained channel visibility and let you pre-assign new members at invite time.
- A team belongs to exactly one org.
- A user can be in any number of teams within an org.
- A channel can be team-scoped; only team members (plus org Owners/Admins) see its items.
Data model
Migration 0017_teams.sql adds:
| Table | Key columns |
|---|---|
teams | id, org_id, name, slug, created_at — unique on (org_id, slug) |
team_memberships | id, team_id, user_id, role (lead / member), created_at — unique on (team_id, user_id) |
Related team_id foreign keys:
channels.team_id— nullable;ON DELETE SET NULLso a deleted team downgrades its channels to org-wide visibility rather than breaking them.invitations.team_id— nullable; the accept handler adds the accepted user to that team automatically (see Invitations).
Endpoints
All under /api/admin/teams. All require the caller to be authenticated in the target org.
| Method & Path | Permission | Purpose |
|---|---|---|
GET /api/admin/teams | teams.manage_members | List teams in the org |
POST /api/admin/teams | teams.create | Create a team — gated on max_teams plan limit |
PATCH /api/admin/teams/{team_id} | teams.manage_members | Rename or change slug |
DELETE /api/admin/teams/{team_id} | teams.delete | Delete the team |
GET /api/admin/teams/{team_id}/members | teams.manage_members | List members |
POST /api/admin/teams/{team_id}/members | teams.manage_members | Add a member with role lead or member |
DELETE /api/admin/teams/{team_id}/members/{user_id} | teams.manage_members | Remove from team |
Create a team
curl -X POST https://api.klaxon.sh/api/admin/teams \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"Platform","slug":"platform"}'When the plan limit is hit you'll get a 402 Payment Required with plan_limit_exceeded — see Billing.
Add a member
curl -X POST https://api.klaxon.sh/api/admin/teams/$TEAM_ID/members \
-H "Authorization: Bearer $TOKEN" \
-d '{"user_id":"…","role":"member"}'Users must already belong to the same org (via org_memberships) — the handler rejects cross-org adds.
Team-scoped channels
Set team_id on a channel to restrict it:
curl -X PATCH https://api.klaxon.sh/api/channels/$CHANNEL_ID \
-H "Authorization: Bearer $TOKEN" \
-d '{"team_id":"…"}'Visibility rules enforced in the channel handler:
- If the channel's
team_idisNULL→ all org members see it (subject to the channel's ownmode). - If it's set → only users in
team_membershipsfor that team see it. Org Owners and Admins always see it (for incident response). - If the team is deleted,
ON DELETE SET NULLreverts the channel to case 1 automatically.
This means deleting a team is always safe — no items are lost, they just become visible at the org level until an admin re-scopes them.
Invite straight into a team
An invitation can carry a team_id:
curl -X POST https://api.klaxon.sh/api/admin/invitations \
-H "Authorization: Bearer $TOKEN" \
-d '{"email":"new@example.com","role":"member","team_id":"…"}'On accept, the server creates both an org_memberships row and a team_memberships row. If the team was deleted between invite and accept, the team assignment is silently dropped (the org membership still succeeds).
UI surface
The web admin panel exposes a Teams tab with a list, create/edit dialog, and member management. The mobile app currently reads team membership for visibility but doesn't expose admin actions — those live on web.
Related guides
- Channels — open / private / team-scoped visibility
- Invitations —
team_idon invites - Roles & Permissions —
teams.*permission strings - Billing —
max_teamsplan limit