Lumen Docs

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 exist
  • 403 — 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).