Skip to main content

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"
}
  • owner is the id of the project's owner; sharedWith lists its members, each with a role of editor or viewer.
  • status is active, paused or archived (archivedAt is set while archived).
  • visibility is private, organization or global; organization and global allow all users.
  • filesystemType is strongly (filesystemConfig: { initialized }) or github (filesystemConfig: { repoUrl, branch, sshKeyId }).
  • volumeId is the project's own code/data volume.

GET /api/v1/projects​

List all projects accessible to the authenticated user.

Scope: projects:read

Query Parameters

ParameterTypeRequiredDescription
qstringNoSearch by name or description
statusstringNoFilter by status: active, paused, archived
categorystringNoFilter by category
tagstringNoFilter by tag
limitintegerNoNumber of results to return (default: 50, max: 200)
cursorstringNometa.nextCursor of the previous page; omit for the first page
sortstringNoSort 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"
}
}
FieldTypeRequiredDescription
namestringYesProject name (leading and trailing spaces are trimmed). Must not match another project or volume in your organization, ignoring case
descriptionstringYesProject description
filesystemTypestringNostrongly (platform-managed, the default) or github. Any value other than github is treated as strongly
githubConfigobjectRequired 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
tagsstring[]NoTags (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: name or description is missing (listed in details) or not a string; filesystemType is github without a githubConfig; githubConfig is missing a field or has an extra one; repoUrl is not in SSH form; or sshKeyId is not one of your keys.
  • 409 name-taken with the message A project named "..." already exists. Project and volume names must be unique. (or A 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

ParameterTypeRequiredDescription
idstringYesProject 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

ParameterTypeRequiredDescription
idstringYesProject ID

Request Body

{
"name": "Updated ML Pipeline",
"description": "Updated description",
"category": "machine-learning",
"tags": ["ml", "v2"]
}
FieldTypeRequiredDescription
namestringNoProject 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.
descriptionstringNoProject description
categorystringNoOne of machine-learning, data-analysis, development, research
tagsarrayNoTag strings; replaces the project's tags
iconstringNoIcon name
readmestringNoThe project README (Markdown)
isPublicbooleanNotrue 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

ParameterTypeRequiredDescription
idstringYesProject ID

Request Body

{ "newOwnerId": "user_abc123" }
FieldTypeRequiredDescription
newOwnerIdstringYesUser 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

ParameterTypeRequiredDescription
idstringYesProject ID

Query Parameters

ParameterTypeRequiredDescription
volumestringYesdelete: 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

ParameterTypeRequiredDescription
idstringYesProject 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

ParameterTypeRequiredDescription
idstringYesProject 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

ParameterTypeRequiredDescription
idstringYesProject 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" }
}
FieldDescription
jobs.definedJobs defined in the project
jobs.runs, succeeded, failed, runningThe jobs' runs and how they went (a timed-out run counts as failed; a pending run as running)
jobs.lastRunAtWhen the latest run started (UTC), or null
jobs.runHoursHours the runs took; a run still going counts to now
workspaces.total, runningThe project's workspaces and how many are running
workspaces.hoursRunHours the workspaces ran, one for each hour a workspace ran
volumes.countThe project's volumes
volumes.dataFiles, dataBytesFiles and bytes of the volumes' data (the current version of each file)
cost.workspaces, jobs, totalCost in USD of the workspaces and job runs
cost.throughThe 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

ParameterTypeRequiredDescription
idstringYesProject ID

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results to return (default: 50, max: 200)
cursorstringNometa.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

ParameterTypeRequiredDescription
idstringYesProject 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

ParameterTypeRequiredDescription
idstringYesProject ID

Request Body

{
"email": "bob@example.com",
"role": "editor",
"userId": "user_789"
}
FieldTypeRequiredDescription
emailstringYesEmail address of the new member
rolestringYesMember role: viewer, editor, admin
userIdstringNoUser 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

ParameterTypeRequiredDescription
projectIdstringYesProject ID
idstringYesUser 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

ParameterTypeRequiredDescription
projectIdstringYesProject ID
idstringYesUser ID of the collaborator

Request Body

{
"role": "admin"
}
FieldTypeRequiredDescription
rolestringYesNew 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

ParameterTypeRequiredDescription
idstringYesProject 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

ParameterTypeRequiredDescription
idstringYesProject ID

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoNumber of results to return (default: 50, max: 200)
cursorstringNometa.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 parameterTypeDescription
statusstringProject status, for example archived (default: every project not archived)
categorystringProject category
qstringName 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