Every route on this page is under /api/admin/* and requires the signed-in user’s role to be admin. See Admin for the settings page these back.
Users
GET /api/admin/users
GETAdmin
Response: { "users": [{ "id": "...", "email": "...", "name": "...", "color": "bg-blue-500", "role": "user", "disabledAt": null, "createdAt": "..." }] }
POST /api/admin/users
POSTAdmin
Creates a user and their Personal workspace. There’s no public sign-up — this is the only way a new account gets created after /setup.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | 1–80 characters |
email |
string | Yes | Fails with 409 CONFLICT if already in use |
password |
string | Yes | 8–200 characters |
role |
admin | user |
Yes | |
color |
string | No | One of the eight user colors; auto-assigned if omitted |
Response: 201 with the new user (not wrapped): { "id": "...", "email": "...", "name": "...", "color": "...", "role": "user" }.
PATCH /api/admin/users/:userId
PATCHAdmin
Request body — all optional:
| Field | Type | Notes |
|---|---|---|
name |
string | |
password |
string | |
role |
admin | user |
|
color |
string | |
disabled |
boolean | true sets disabledAt to now and blocks login; false clears it |
Response: { "ok": true }
DELETE /api/admin/users/:userId
DELETEAdmin
Deletes the user and, since a workspace’s createdBy cascades, every workspace they created — including shared ones other people are members of, and all logs, tags, tasks, and attachments in those workspaces. There’s no confirmation at the API layer; the UI is what asks twice.
Response: { "ok": true }
Workspaces
GET /api/admin/workspaces
GETAdmin
Every workspace on the instance, with its member list — unlike GET /api/workspaces, which only returns the signed-in user’s own.
Response
{
"workspaces": [
{ "workspace": { "id": "...", "slug": "platform-team", "name": "Platform Team", "color": "bg-violet-500", "icon": "server", "kind": "shared", "templateMode": "today_only", "createdBy": "...", "createdAt": "..." }, "members": [{ "userId": "...", "role": "owner" }, { "userId": "...", "role": "member" }] }
]
}POST /api/admin/workspaces
POSTAdmin
Creates a shared workspace. Personal workspaces are never created through this route — they’re created automatically alongside a user.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | 1–80 characters |
color |
string | No | One of the workspace colors |
icon |
string | No | One of the workspace icons |
memberIds |
string[] | No | Additional members beyond the admin creating it |
Response: 201 with the new workspace (not wrapped).
PATCH /api/admin/workspaces/:workspaceId
PATCHAdmin
Renames a workspace or changes its appearance.
Request body
| Field | Type | Required |
|---|---|---|
name |
string | Yes |
color |
string | Yes |
icon |
string | Yes |
Response: { "ok": true }
PUT /api/admin/workspaces/:workspaceId/members
PUTAdmin
Replaces the workspace’s member list wholesale — this is a set, not a diff. Fails with 409 CONFLICT on a Personal workspace, which can’t take more than its one member.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
userIds |
string[] | Yes | The creator is always kept as owner even if omitted here |
Response: { "ok": true }
DELETE /api/admin/workspaces/:workspaceId
DELETEAdmin
Deletes the workspace and, by cascade, every log, tag, task, and attachment in it, plus its files under the storage backend’s workspaces/<slug>/ prefix.
Response: { "ok": true }
Instance Settings
A flat key-value store (settings table, both keys and values are strings) for instance-wide configuration. GET/PUT accept and return whatever keys are there — the API doesn’t enforce a fixed shape.
GET /api/admin/instance
GETAdmin
Response: { "settings": { "defaultPageSize": "10", "maxAttachmentSize": "10485760", "allowedFileTypes": "image/png,image/jpeg,...", "storageBackend": "local", "mirrorPath": "" } }
PUT /api/admin/instance
PUTAdmin
Upserts settings — keys not included in the body are left untouched.
Request body: any { "key": "value" } object (all values must be strings). The settings page in the UI writes these keys:
| Key | Effect |
|---|---|
defaultPageSize |
Page size for every paginated list endpoint |
maxAttachmentSize |
Overrides the MAX_ATTACHMENT_SIZE env var, in bytes |
allowedFileTypes |
Overrides the ALLOWED_FILE_TYPES env var — comma-separated MIME types |
storageBackend, mirrorPath |
Recorded for the UI to display; the active storage backend itself is still chosen by server config/env at boot, not switched live from this endpoint |
Response: { "ok": true }