Lumen Docs

Users & departments

User CRUD, department CRUD, and the relationships between them.

Users

All /users/* endpoints require admin or superadmin platform role, except /users/me which requires only auth.

GET /users

List all users.

Query params:

  • search — case-insensitive match on name or email
  • platformRole — filter none|admin|engineer|superadmin
  • orgPosition — filter member|manager|ceo
  • departmentId — filter by department
  • limit / offset — pagination

Response 200:

{
  "users": [
    {
      "id": "uuid",
      "email": "...",
      "name": "...",
      "platformRole": "admin",
      "orgPosition": "manager",
      "departmentId": "uuid",
      "department": {"id": "...", "name": "...", "color": "..."},
      "avatarColor": "#93a4c4",
      "status": "active",
      "lastLoginAt": "...",
      "createdAt": "..."
    }
  ],
  "total": 42
}

POST /users

Create a user. The caller must be admin or superadmin.

Request:

{
  "email": "new@company.com",
  "name": "New User",
  "password": "min 8 chars",
  "platformRole": "none" | "admin" | "engineer",   // superadmin rejected
  "orgPosition": "member" | "manager" | "ceo",
  "departmentId": "uuid"                            // optional; omit for platform staff & CEO
}

Response 201:

{ "user": {...} }

Errors:

  • 400 department_not_found — referenced department doesn't exist
  • 403 forbidden_role — attempted to create superadmin (only bootstrap can do that)
  • 409 ceo_exists — attempted to create a second CEO (CEO is a singleton)
  • 409 email_exists — email already taken

GET /users/:id

Fetch one user.

PATCH /users/:id

Update a user. Restrictions:

  • Only superadmin can change another user's platformRole to/from admin or engineer
  • Nobody can set platformRole = superadmin via this endpoint
  • CEO transfer (changing orgPosition to ceo while another exists) returns 409 ceo_exists
{
  "name": "...",
  "email": "...",
  "platformRole": "...",
  "orgPosition": "...",
  "departmentId": "...",
  "status": "active" | "inactive",
  "password": "..."   // if present, re-hashes
}

DELETE /users/:id

Delete a user. Cascades: removes their grants, group memberships, project memberships. Conversations and messages are retained but user is set to null on them.

  • Cannot delete the superadmin
  • Cannot delete yourself

GET /users/me

Returns the current authenticated user. Used by the web app's auth context for hydration.

Departments

/departments/* endpoints require admin/superadmin, except GET-only endpoints also allow department manager or CEO.

GET /departments

List departments with member counts.

Response 200:

{
  "departments": [
    {
      "id": "uuid",
      "name": "Engineering",
      "color": "#2a6fdb",
      "description": "...",
      "_count": {"members": 8, "groups": 3, "grants": 12}
    }
  ]
}

POST /departments

Create a department.

Request:

{
  "name": "Design",
  "color": "#6b46c1",
  "description": "Product + visual designers"
}

Name must be unique. Returns 201 with the created department.

PATCH /departments/:id

Update a department. admin/superadmin or the department's own manager.

DELETE /departments/:id

Delete a department. Blocked if:

  • Any users belong to it (reassign them first)
  • Any groups exist under it (delete them first)
  • Any grants reference it (revoke them first)

Returns 409 with the blocker reason.

GET /departments/:id/members

List members of a department.

POST /departments/:id/members

Add existing users to a department. Requires admin/superadmin.

Request:

{ "userIds": ["uuid", "uuid"] }

A user can only belong to one department — POSTing a user already in another dept 409s unless replace: true is passed:

{ "userIds": [...], "replace": true }

DELETE /departments/:id/members/:userId

Remove a user from the department (sets their departmentId = null).

Department managers

There's no separate "manager" role on the department side. A user is the department's manager if:

  • user.orgPosition === "manager"
  • user.departmentId === <this dept's id>

The requireCeoOrManager(paramKey) middleware enforces this on endpoints where the dept's manager should be able to act (e.g., creating groups, managing members within the dept).

Singleton constraints

  • CEO: at most one user has orgPosition = 'ceo' (enforced by single_ceo partial unique index)
  • Superadmin: at most one user has platformRole = 'superadmin' (enforced by single_superadmin partial unique index)

Both are non-transferable in the dev phase. The wizard creates them; afterwards they can be renamed but not reassigned.