6.3 KiB
Projects
Projects are organizational containers that group channels, knowledge bases, notes, and files. They follow the scope model (personal, team, global).
All endpoints require authentication (Authorization: Bearer <token>).
Admin endpoints additionally require role=admin.
Project Object
{
"id": "uuid",
"name": "Q3 Research",
"description": "...",
"color": "#3b82f6",
"icon": "folder",
"scope": "personal|team|global",
"owner_id": "uuid",
"team_id": "uuid|null",
"workspace_id": "uuid|null",
"is_archived": false,
"settings": {},
"channel_count": 5,
"kb_count": 2,
"note_count": 3,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
| Field | Type | Notes |
|---|---|---|
id |
uuid | PK, auto-generated |
name |
string | Required, max 200 chars |
description |
string | Optional |
color |
string? | CSS hex color, max 7 chars |
icon |
string? | Icon identifier, max 50 chars |
scope |
enum | personal, team, global |
owner_id |
uuid | Set from JWT, immutable |
team_id |
uuid? | Set for team-scoped projects |
workspace_id |
uuid? | Linked workspace |
is_archived |
bool | Default false |
settings |
object | JSONB, default {} |
channel_count |
int | Computed, omitted when 0 |
kb_count |
int | Computed, omitted when 0 |
note_count |
int | Computed, omitted when 0 |
created_at |
datetime | Auto-set |
updated_at |
datetime | Auto-updated on change |
Project CRUD
List Projects
GET /projects → { "data": [...] }
Query params: ?include_archived=true to include archived projects.
Returns projects the user can access: personal (own), team (member), and global. Ordered by name.
Create Project
POST /projects → 201, project object
{
"name": "My Project",
"description": "Optional description",
"color": "#3b82f6",
"icon": "folder"
}
| Field | Required | Notes |
|---|---|---|
name |
yes | Max 200 chars |
description |
no | |
color |
no | CSS hex |
icon |
no | Icon identifier |
Scope is set to personal and owner_id is taken from the JWT.
Returns the created project object (bare, no envelope).
Get Project
GET /projects/:id → project object
Returns bare project object (no envelope). 404 if not found or not accessible.
Update Project
PUT /projects/:id → updated project object
Partial update — only supplied fields are changed.
{
"name": "New Name",
"description": "Updated",
"color": "#ef4444",
"icon": "star",
"is_archived": true,
"workspace_id": "uuid|null",
"settings": { "key": "value" }
}
Settings are merged (overlay, not replace). Returns the refreshed project object after update.
Delete Project
DELETE /projects/:id → { "message": "project deleted" }
Only the project owner or an admin can delete. Channels get
project_id set to NULL; junction rows cascade.
Channel Association
GET /projects/:id/channels → { "data": [...] }
POST /projects/:id/channels ← { "channel_id", "position" }
DELETE /projects/:id/channels/:channelId → { "message": "channel removed" }
PUT /projects/:id/channels/reorder ← { "channel_ids": ["uuid1", "uuid2"] }
A channel can only belong to one project. POST performs an atomic
move if the channel is already in a different project. The user must
own the channel being added.
Channel association object:
{
"project_id": "uuid",
"channel_id": "uuid",
"position": 0,
"folder": "",
"added_at": "2025-01-15T10:30:00Z"
}
POST request:
| Field | Required | Notes |
|---|---|---|
channel_id |
yes | UUID of channel to add |
position |
no | Sort order (default 0) |
KB Association
GET /projects/:id/knowledge-bases → { "data": [...] }
POST /projects/:id/knowledge-bases ← { "kb_id", "auto_search" }
DELETE /projects/:id/knowledge-bases/:kbId → { "message": "KB removed" }
KBs bound to a project are automatically available to every channel
in that project (resolution chain injection at completion time).
Upsert on conflict — re-posting updates auto_search.
KB association object:
{
"project_id": "uuid",
"kb_id": "uuid",
"auto_search": true,
"added_at": "2025-01-15T10:30:00Z",
"name": "KB Name"
}
name is enriched via JOIN from knowledge_bases.name.
POST request:
| Field | Required | Notes |
|---|---|---|
kb_id |
yes | UUID of knowledge base |
auto_search |
no | Default false |
Note Association
GET /projects/:id/notes → { "data": [...] }
POST /projects/:id/notes ← { "note_id" }
DELETE /projects/:id/notes/:noteId → { "message": "note removed" }
Note association object:
{
"project_id": "uuid",
"note_id": "uuid",
"added_at": "2025-01-15T10:30:00Z",
"title": "Note Title"
}
title is enriched via JOIN from notes.title.
Insert uses ON CONFLICT DO NOTHING (idempotent).
Project Files
GET /projects/:id/files → { "files": [...], "count": N }
POST /projects/:id/files ← multipart/form-data
Project-level file uploads (distinct from workspace files and channel
attachments). Note: GET uses "files" key, not "data".
Admin Project Management
GET /admin/projects → { "data": [...] }
DELETE /admin/projects/:id → { "message": "project deleted" }
Query params: ?include_archived=true.
Cross-instance visibility for platform admins. Admin list returns an enriched object with extra fields:
{
"id": "uuid",
"name": "...",
"description": "...",
"scope": "personal",
"owner_id": "uuid",
"team_id": null,
"is_archived": false,
"created_at": "...",
"updated_at": "...",
"channel_count": 5,
"kb_count": 2,
"note_count": 3,
"owner_name": "jdoe"
}
Note: admin list object omits color, icon, workspace_id,
and settings (uses a local struct, not the full model).