Skip to content
stage

Projects

Project:

{
  "id": 1,
  "t3ProjectId": "proj_abc",
  "title": "stage",
  "workspaceRoot": "/home/user/workspace/stage",
  "source": "app",
  "archivedAt": null,
  "createdAt": "2026-11-08T10:00:00Z",
  "updatedAt": "2026-11-08T10:00:00Z",
  "lastActivityAt": "2026-11-08T11:30:00Z"
}

lastActivityAt: latest of the project's thread updatedAt / settledAt (incl. subagents) and task updatedAt; null when none.

T3 is the source of truth: title, workspaceRoot and archivedAt follow T3 on every sync (empty T3 title → workspaceRoot). Projects are read-only here; rename/archive in T3. A project deleted in T3 stays (tasks reference it) and is archived (archivedAt = T3 deletedAt unless already archived).

GET /api/projects

Query: archived = true (only archived) | false (default, only active) | all.

200 { "projects": Project[] } ordered by title.

POST /api/projects

Body: { "title": string, "workspaceRoot": string } (both required, trimmed; absolute roots are path-cleaned, e.g. trailing / dropped).

  • An app project with the same workspaceRoot exists → 409.
  • A T3 project with the same workspaceRoot exists (already imported, or found after refreshing t3_project_list) → return it as mirrored (source: "t3"): title and archive state stay T3's; the requested title is ignored. Projects deleted in T3 do not count: their root can be created again.
  • Otherwise create it in T3 (t3_project_create) → source: "app".

201 Project. 502 T3 error. 503 not connected.

GET /api/projects/:id

200 Project. 404.