Projects
Endpoints under /projects/*. All require auth. Access is resolver-based — see resolver priority.
GET /projects
List every project the user can access.
Response 200:
{
"projects": [
{
"id": "uuid",
"name": "Marketing",
"description": null,
"color": "#e0a020",
"isPrivate": false,
"ownerId": "uuid",
"members": [...],
"_count": { "documents": 12, "conversations": 5 },
"accessTier": "full",
"accessSource": "platform",
...
}
]
}
accessTier and accessSource are computed per-user by the resolver. Client can use them to hide destructive actions for use-tier users.
Platform staff see every project (via platform source, full tier). Regular users see projects they're granted or public projects (with source public, tier use).
GET /projects/:id
Fetch a single project with members and counts.
Response 200:
{
"project": {
"id": "uuid",
"name": "Marketing",
...,
"members": [
{
"projectId": "uuid",
"userId": "uuid",
"addedAt": "...",
"user": {
"id": "uuid", "name": "...", "email": "...",
"avatarUrl": null,
"platformRole": "admin",
"orgPosition": "member",
"departmentId": "uuid"
}
}
],
"_count": { "documents": 12, "conversations": 5, "chunks": 480 }
}
}
Errors:
404— project doesn't exist403— resolver returned null (no access)
POST /projects
Create a new project. Requires admin or superadmin platform role.
Request:
{
"name": "Customer Research",
"description": "Interview transcripts and synthesis",
"color": "#6b46c1",
"isPrivate": true,
"instructions": "Answer in bullet points, cite every claim"
}
Response 201:
{
"project": {
"id": "uuid", "name": "Customer Research", ...,
"ownerId": "<caller-id>",
"createdBy": "<caller-id>"
}
}
The caller becomes the owner. Owner gets automatic full tier via priority 3 of the resolver.
PATCH /projects/:id
Update project fields. Requires edit tier.
Request (any subset of):
{
"name": "...",
"description": "...",
"instructions": "...",
"color": "#...",
"isPrivate": true,
"settings": { "model": "...", "temperature": 0.3, "topK": 8 },
"ownerId": "new-owner-uuid" // requires "full" tier
}
ownerId transfer requires full tier. Backend validates the new owner is admin/superadmin (enforced application-side).
DELETE /projects/:id
Delete project + cascade chunks, documents, conversations, grants. Requires full tier (owner, admin, superadmin).
Response 200:
{ "success": true, "id": "uuid" }
Destructive. Frontend confirms via window.confirm() before firing.
Members
Legacy ProjectMember table is still present for historical reasons. The modern access path is ProjectGrant. When a grant is created for a user, a ProjectMember row is auto-synced so the project detail page can render "who is involved" without a complex query.
DELETE /projects/:id/members/:userId
Remove a user from the project's visible member list. Requires edit tier. Does NOT revoke grants — to revoke, use the grant endpoints.
Grants (under /projects/:id/grants)
See Grants API for the full CRUD of direct/group/department grants.
Typical flows
Create project + grant access to a group:
# 1. Create
PROJECT=$(curl -X POST -H "Auth..." -d '{"name":"Research","color":"#6b46c1"}' .../projects | jq -r .project.id)
# 2. Grant the Design group read-only access
curl -X POST -H "Auth..." .../projects/$PROJECT/grants \
-d '{"targetType":"group","targetId":"<group-id>","tier":"use"}'
Transfer ownership:
curl -X PATCH -H "Auth..." .../projects/$PROJECT \
-d '{"ownerId":"<new-owner-id>"}'
Make public:
curl -X PATCH -H "Auth..." .../projects/$PROJECT \
-d '{"isPrivate":false}'
Now every authenticated user gets use tier (resolver priority 7).