This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ICD/projects.md
2026-03-13 23:47:06 +00:00

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).