# Teams & Access Control

### My Teams

```
GET /teams/mine                     → { "data": [...] }
```

**Auth:** Authenticated user.

Returns teams the current user is a member of (active teams only).

Each team object:

```json
{
  "id": "uuid",
  "name": "Engineering",
  "description": "...",
  "is_active": true,
  "settings": "{}",
  "my_role": "admin|member",
  "member_count": 5
}
```

### Team Administration

**Team CRUD (platform admin):**

```
GET    /admin/teams                  → { "data": [...], "total": N, "page": N, "per_page": N }
POST   /admin/teams                  ← { "name", "description" }  → { "id", "name" }
GET    /admin/teams/:id              → team object (unwrapped)
PUT    /admin/teams/:id              ← { "name"?, "description"?, "is_active"?, "settings"? }
DELETE /admin/teams/:id
```

**Auth:** `RequireAdmin` (platform admin).

`POST` returns `201`. `PUT`/`DELETE` return `{"ok": true}`. `PUT` with
no fields returns `400`. Duplicate name returns `409`.

**Members (admin routes):**

```
GET    /admin/teams/:id/members         → { "data": [...] }
POST   /admin/teams/:id/members         ← { "user_id", "role" }  → { "id" }
PUT    /admin/teams/:id/members/:memberId   ← { "role" }
DELETE /admin/teams/:id/members/:memberId
```

**Auth:** `RequireAdmin`.

`role` must be `admin` or `member`. Duplicate membership returns `409`.

**Team-scoped routes** (team admin self-service):

```
GET    /teams/:teamId/members
POST   /teams/:teamId/members        ← { "user_id", "role" }
PUT    /teams/:teamId/members/:memberId
DELETE /teams/:teamId/members/:memberId
```

**Auth:** `RequireTeamAdmin`.

Same handlers as admin routes; the `getTeamID()` helper reads either
`:id` or `:teamId`.

**Team Providers:**

```
GET    /teams/:teamId/providers          → { "data": [...], "allow_team_providers": bool }
POST   /teams/:teamId/providers          ← { "name", "provider", "endpoint", "api_key", ... }
PUT    /teams/:teamId/providers/:id
DELETE /teams/:teamId/providers/:id
GET    /teams/:teamId/providers/:id/models  → { "models": [...], "provider": "name" }
```

**Auth:** `RequireTeamAdmin`.

`GET` returns provider configs scoped to the team. The `models` endpoint
performs a live query to the upstream provider.

**Team Models:**

```
GET /teams/:teamId/models           → { "models": [...] }
```

**Auth:** `RequireTeamAdmin`.

Returns all models available to this team: global admin models with
`visibility IN ('enabled', 'team')` plus live-queried team provider
models with `source` field (`"global"` or `"team"`).

**Team Groups:**

```
GET /teams/:teamId/groups           → { "data": [...] }
```

**Auth:** `RequireTeamAdmin`.

Returns groups scoped to this team.

**Team Personas:** See personas.md §Team Personas.

**Team Roles:**

```
GET    /teams/:teamId/roles          → { "data": {...} }
PUT    /teams/:teamId/roles/:role    ← RoleConfig object
DELETE /teams/:teamId/roles/:role
```

**Auth:** `RequireTeamAdmin`.

`GET` returns the team's `model_roles` settings map (composite object,
not an array). Initially empty `{}`. Each key is a role name, value is
a `RoleConfig`. `PUT` validates the role name. `DELETE` removes the
override, falling back to the global role definition.

**Team Audit:**

```
GET /teams/:teamId/audit             → { "data": [...], "total": N, "page": N, "per_page": N }
GET /teams/:teamId/audit/actions     → { "actions": [...] }
```

**Auth:** `RequireTeamAdmin`.

Audit log is scoped to actions performed by team members. Supports
query filters: `?action=`, `?actor_id=`, `?resource_type=`.

**Team Usage:**

```
GET /teams/:teamId/usage             → { "totals": {...}, "results": [...] }
```

**Auth:** `RequireTeamAdmin`.

Returns usage against team-owned providers. Supports query params for
date range filtering.

### Groups & Resource Grants

Groups are ACL containers that decouple access from team membership.
A resource grant controls who can see a Persona or KB beyond its base
scope.

**My Groups:**

```
GET /groups/mine                     → { "data": [...] }
```

**Auth:** Authenticated user.

**Admin Group CRUD:**

```
GET    /admin/groups                 → { "data": [...] }
POST   /admin/groups                 ← { "name", "scope", "team_id"? }
GET    /admin/groups/:id             → group object (unwrapped)
PUT    /admin/groups/:id             ← { "name"?, "description"?, "permissions"?, ... }
DELETE /admin/groups/:id
```

**Auth:** `RequireAdmin`.

`POST` returns `201`. Duplicate name returns `409`. Deleting a
system-sourced group (e.g. Everyone) returns `400`.

**Group Members:**

```
GET    /admin/groups/:id/members     → { "data": [...] }
POST   /admin/groups/:id/members     ← { "user_id" }
DELETE /admin/groups/:id/members/:userId
```

**Auth:** `RequireAdmin`.

**Resource Grants:**

```
GET    /admin/grants/:type/:id       → grant object (unwrapped)
PUT    /admin/grants/:type/:id       ← { "grant_scope", "granted_groups"?: [...] }
DELETE /admin/grants/:type/:id
```

**Auth:** `RequireAdmin`.

`:type` is `persona` or `kb`. `:id` is the resource ID.

Grant scopes:

| `grant_scope` | Visibility |
|---------------|-----------|
| `team_only` | Only the owning team |
| `global` | All authenticated users |
| `groups` | Members of specified groups |

Setting `grant_scope: "groups"` without `granted_groups` returns `400`.

### Permissions

Groups carry fine-grained permissions. Permission resolution:
union of all group permissions + Everyone group permissions.

**List all permissions:**

```
GET /admin/permissions               → { "permissions": ["model.use", "kb.read", ...] }
```

**Auth:** `RequireAdmin`.

**Get user's effective permissions:**

```
GET /admin/users/:id/permissions     → { "permissions": [...], "user_id": "...", "groups": [...] }
```

**Auth:** `RequireAdmin`.

Returns the resolved permission set and contributing group IDs
(always includes the Everyone group).

**Group permission fields** (set via `PUT /admin/groups/:id`):

```json
{
  "permissions": ["model.use", "kb.read", "channel.create"],
  "token_budget_daily": 100000,
  "token_budget_monthly": 3000000,
  "allowed_models": ["claude-sonnet-4-20250514", "gpt-4o"]
}
```

| Field | Type | Description |
|-------|------|-------------|
| `permissions` | string[] | Permission constants granted to members |
| `token_budget_daily` | int/null | Daily token ceiling (NULL = unlimited) |
| `token_budget_monthly` | int/null | Monthly token ceiling (NULL = unlimited) |
| `allowed_models` | string[]/null | Model allowlist (NULL = unrestricted) |

**Everyone group:** System-seeded group (ID `00000000-0000-0000-0000-000000000001`)
whose permissions apply to all authenticated users implicitly (no membership
row needed). Editable by admins, cannot be deleted (`source=system`).

**RequirePermission middleware:** Routes gated by permission check user's
resolved permission set. Returns 403 if the required permission is not present.

See [enums.md](enums.md) for the full permission constant list.
