Projects
Create, manage, and collaborate on projects. Projects serve as organizational containers for workspaces, volumes, and team collaboration.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Project Object
{
"_id": "proj_abc123",
"name": "ML Training Pipeline",
"description": "End-to-end machine learning training and evaluation project",
"icon": "folder",
"status": "active",
"category": "machine-learning",
"visibility": "private",
"organizationId": "org_xyz",
"owner": "user_456",
"sharedWith": [
{
"userId": "user_789",
"email": "bob@example.com",
"role": "editor",
"grantedAt": "2025-01-20T09:00:00Z",
"grantedBy": "user_456"
}
],
"filesystemType": "strongly",
"filesystemConfig": { "initialized": true },
"volumeId": "vol_def001",
"tags": ["ml", "production"],
"readme": "# ML Training Pipeline",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z",
"lastAccessedAt": "2025-02-01T14:22:00Z"
}
owneris the id of the project's owner;sharedWithlists its members, each with aroleofeditororviewer.statusisactive,pausedorarchived(archivedAtis set while archived).visibilityisprivate,organizationorglobal;organizationandglobalallow all users.filesystemTypeisstrongly(filesystemConfig: { initialized }) orgithub(filesystemConfig: { repoUrl, branch, sshKeyId }).volumeIdis the project's own code/data volume.
GET /api/v1/projects
List all projects accessible to the authenticated user.
Scope: projects:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No | Search by name or description |
status | string | No | Filter by status: active, paused, archived |
category | string | No | Filter by category |
tag | string | No | Filter by tag |
limit | integer | No | Number of results to return (default: 50, max: 200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
sort | string | No | Sort field, prefixed with - for descending (default: -createdAt) |
Response 200 OK
{
"data": [
{
"_id": "proj_abc123",
"name": "ML Training Pipeline",
"description": "End-to-end machine learning training and evaluation project",
"icon": "folder",
"status": "active",
"category": "machine-learning",
"visibility": "private",
"organizationId": "org_xyz",
"owner": "user_456",
"sharedWith": [],
"filesystemType": "strongly",
"filesystemConfig": { "initialized": true },
"volumeId": "vol_def001",
"tags": ["ml", "production"],
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z"
}
],
"meta": {
"total": 12,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
POST /api/v1/projects
Create a new project. The project gets its own code/data volume with the same name.
Scope: projects:write
Request Body
{
"name": "ML Training Pipeline",
"description": "End-to-end machine learning training and evaluation project",
"tags": ["ml", "production"]
}
With a GitHub repository as the code filesystem:
{
"name": "Churn Model",
"description": "Churn model from our GitHub repo",
"filesystemType": "github",
"githubConfig": {
"repoUrl": "git@github.com:acme/churn-model.git",
"branch": "main",
"sshKeyId": "k1"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Project name (leading and trailing spaces are trimmed). Must not match another project or volume in your organization, ignoring case |
description | string | Yes | Project description |
filesystemType | string | No | strongly (platform-managed, the default) or github. Any value other than github is treated as strongly |
githubConfig | object | Required when filesystemType is github | { repoUrl, branch, sshKeyId }, all strings. repoUrl must be in SSH form (git@github.com:user/repo.git); sshKeyId is one of your GitHub SSH keys |
tags | string[] | No | Tags (default: []) |
Any other field is ignored, as is a tags value that is not an array. A new project starts active and private, with category development.
Response 201 Created
The new project, with its own volume (volumeId).
Errors:
400 validation-error:nameordescriptionis missing (listed indetails) or not a string;filesystemTypeisgithubwithout agithubConfig;githubConfigis missing a field or has an extra one;repoUrlis not in SSH form; orsshKeyIdis not one of your keys.409 name-takenwith the messageA project named "..." already exists. Project and volume names must be unique.(orA volume named ...) when the name is taken (ignoring case, among the organization's projects and volumes).
GET /api/v1/projects/:id
Get a single project by ID.
Scope: projects:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Response 200 OK
Returns the full Project object.
PATCH /api/v1/projects/:id
Update a project's details, or whether all users may join it. Any field not listed below is refused with 400: archiving is the archive and restore endpoints, people are the member endpoints.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Request Body
{
"name": "Updated ML Pipeline",
"description": "Updated description",
"category": "machine-learning",
"tags": ["ml", "v2"]
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Project name, unique among the organization's projects and volumes (ignoring case). Surrounding spaces are removed; a blank name is refused. The project's volume keeps its own name. |
description | string | No | Project description |
category | string | No | One of machine-learning, data-analysis, development, research |
tags | array | No | Tag strings; replaces the project's tags |
icon | string | No | Icon name |
readme | string | No | The project README (Markdown) |
isPublic | boolean | No | true lets all users join the project: every user who can see the organization's resources can find and use it, while editing stays with its owner and editors. false makes it private, reached only by its owner and members. |
Response 200 OK: the project as it now is.
POST /api/v1/projects/:id/transfer
Reassign a project to another user in its organization (the same as Reassign owner at the bottom of the project's Permissions tab). The new owner owns the project and its code/data volume and leaves the member list; the previous owner stays on as an editor. Workspaces and jobs stay with the users who created them. Only the owner or an admin can reassign it.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Request Body
{ "newOwnerId": "user_abc123" }
| Field | Type | Required | Description |
|---|---|---|---|
newOwnerId | string | Yes | User ID of the new owner, a user in the project's organization |
Response 200 OK: the project, as GET /projects/:id shows it, owner the new owner and transferredFrom the previous one.
DELETE /api/v1/projects/:id
Delete a project. This action is irreversible. The project's jobs (live runs are stopped), workspaces, run history, board and activity are always deleted. The volume query parameter decides the project's code/data volume and has no default.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
volume | string | Yes | delete: permanently delete the project volume and its code and data. keep: keep it as a shared volume owned by the project owner, code and data intact. |
curl -X DELETE "$BASE/api/v1/projects/$PROJECT_ID?volume=keep" -H "Authorization: Bearer $STRONGLY_API_KEY"
Response 204 No Content
Errors: 400 validation-error when volume is missing or not delete/keep; name-taken when volume=keep and the project owner already has a shared volume with the same name (nothing is deleted).
POST /api/v1/projects/:id/archive
Archive a project. Archived projects are hidden from default listings but can be restored.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Response 200 OK
The project, as GET /projects/:id shows it, status archived.
POST /api/v1/projects/:id/restore
Restore an archived project.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Response 200 OK
The project, as GET /projects/:id shows it, status active.
GET /api/v1/projects/:id/stats
Get a project's statistics, read from its records: its jobs and their runs, its workspaces and the hours they ran, its volumes and their data, and its cost.
Scope: projects:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Response 200 OK
{
"data": {
"jobs": {
"defined": 2,
"runs": 5,
"succeeded": 3,
"failed": 1,
"running": 1,
"lastRunAt": "2026-09-28T16:40:00+00:00",
"runHours": 2.25
},
"workspaces": { "total": 6, "running": 1, "hoursRun": 416 },
"volumes": { "count": 1, "dataFiles": 2, "dataBytes": 1500 },
"cost": {
"workspaces": 36.497734,
"jobs": 0.05,
"total": 36.547734,
"through": "2026-09-28T17:00:00+00:00"
}
},
"meta": { "requestId": "req_abc123" }
}
| Field | Description |
|---|---|
jobs.defined | Jobs defined in the project |
jobs.runs, succeeded, failed, running | The jobs' runs and how they went (a timed-out run counts as failed; a pending run as running) |
jobs.lastRunAt | When the latest run started (UTC), or null |
jobs.runHours | Hours the runs took; a run still going counts to now |
workspaces.total, running | The project's workspaces and how many are running |
workspaces.hoursRun | Hours the workspaces ran, one for each hour a workspace ran |
volumes.count | The project's volumes |
volumes.dataFiles, dataBytes | Files and bytes of the volumes' data (the current version of each file) |
cost.workspaces, jobs, total | Cost in USD of the workspaces and job runs |
cost.through | The latest hour the cost covers (UTC); cost settles hour by hour |
GET /api/v1/projects/:id/activity
Get the activity log for a project, newest first. Returns a paginated list of actions performed within the project. Each entry has a type (for example project_created, workspace_started, volume_created, collaborator_added, ownership_transferred), a description, and sometimes a metadata object.
Scope: projects:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of results to return (default: 50, max: 200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
Response 200 OK
{
"data": [
{
"_id": "act_001",
"projectId": "proj_abc123",
"userId": "user_456",
"type": "workspace_started",
"description": "Workspace Training Environment created",
"createdAt": "2025-02-01T14:22:00Z"
}
],
"meta": {
"total": 87,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
GET /api/v1/projects/:id/members
List the members of a project.
Scope: projects:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Response 200 OK
{
"data": [
{
"userId": "user_456",
"role": "owner",
"isOwner": true
},
{
"userId": "user_789",
"email": "bob@example.com",
"role": "editor",
"grantedAt": "2025-01-20T09:00:00Z",
"grantedBy": "user_456",
"isOwner": false
}
],
"meta": { "requestId": "req_abc123" }
}
The owner comes first, followed by everyone the project is shared with.
POST /api/v1/projects/:id/members
Add a member to a project.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Request Body
{
"email": "bob@example.com",
"role": "editor",
"userId": "user_789"
}
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address of the new member |
role | string | Yes | Member role: viewer, editor, admin |
userId | string | No | User ID (if known; otherwise the user is looked up by email) |
Response 201 Created
The new member, as the project's member list shows them: userId, email, role, grantedAt, grantedBy, isOwner (false).
DELETE /api/v1/projects/:projectId/members/:id
Remove a member from a project. Their workspaces and jobs in the project are stopped and deleted with them; if something still uses one of their workspaces, the request is refused and nothing is deleted.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | Project ID |
id | string | Yes | User ID of the collaborator to remove |
Response 204 No Content
PATCH /api/v1/projects/:projectId/members/:id
Update a member's role on a project.
Scope: projects:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | Project ID |
id | string | Yes | User ID of the collaborator |
Request Body
{
"role": "admin"
}
| Field | Type | Required | Description |
|---|---|---|---|
role | string | Yes | New role: viewer, editor, admin |
Response 200 OK
The member, as GET /projects/:id/members lists it.
{ "data": { "userId": "u2", "role": "editor", "isOwner": false }, "meta": { "requestId": "req_abc123" } }
GET /api/v1/projects/:id/volumes
List the volumes that belong to a project.
Scope: projects:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Response 200 OK
{
"data": [
{
"_id": "vol_def001",
"name": "ml-training-pipeline",
"scope": "local",
"projectId": "proj_abc123",
"organizationId": "org_xyz",
"owner": "user_456",
"code": { "filesystemType": "strongly", "branch": "main", "initialized": true },
"data": { "latestVersion": 0, "lastVersionAt": "2025-02-01T14:22:00Z" },
"isShared": false,
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z"
}
],
"meta": { "requestId": "req_abc123" }
}
GET /api/v1/projects/:id/workspaces
List all workspaces within a project, newest first. Returns a paginated list of workspace records.
Scope: projects:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of results to return (default: 50, max: 200) |
cursor | string | No | meta.nextCursor of the previous page; omit for the first page |
Response 200 OK
{
"data": [
{
"_id": "ws_ghi789",
"name": "Training Environment",
"description": "GPU-enabled workspace for model training",
"projectId": "proj_abc123",
"status": "running",
"environment": { "type": "jupyter", "label": "Jupyter Lab", "port": 8888 },
"owner": "user_456",
"createdAt": "2025-01-16T11:00:00Z",
"updatedAt": "2025-02-01T08:00:00Z"
}
],
"meta": {
"total": 5,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
GET /api/v1/projects/counts
The Projects page's counts for the projects you can see: per project (projects) and in total (total), how many workspaces and jobs, how many running and how many in error. Workspaces count only those you may see: your own, all of a project you own, all for administrators.
Scope: projects:read
| Query parameter | Type | Description |
|---|---|---|
status | string | Project status, for example archived (default: every project not archived) |
category | string | Project category |
q | string | Name or description contains |
GET /api/v1/projects/:id/deletion-impact
Before deleting: whether it can be deleted and, if not, what uses it, as the delete dialog says.
Response 200 OK
{
"data": {
"canDelete": false,
"reason": "in-use",
"message": "... is in use by ... Stop or delete those first.",
"dependents": [{ "kind": "app", "items": [{ "id": "app-abc", "name": "forecast-dashboard" }] }]
}
}
When it can be deleted, canDelete is true and dependents is empty.
Then delete with DELETE /api/v1/projects/:id?volume=delete or ?volume=keep.
Scope: projects:read
GET /api/v1/projects/:projectId/members/:id/removal-impact
Before removing a member: their workspaces and jobs in the project, by name ({ workspaces: [{ _id, name, status }], jobs: [{ _id, name }] }), as the remove confirmation lists them.
Scope: projects:read