Volumes
Create, manage, and share code/data volumes: the durable storage your workspaces and jobs work in. A project's volume has two halves that mount together: a git-versioned code half and a per-file versioned data half. A volume created as shared holds the data half only (its code is absent); a project's volume keeps its code when it is shared, or kept after its project is deleted, read-only. Code is written only in a project's own volume, in its project's workspaces; wherever a project's volume is shared, its code is read-only, and a write to a shared volume's code is refused with 403. Every workspace and job run in a project mounts the project's volume at /volumes/local/<name>/{code,data}, and only the shared volumes chosen for it (sharedVolumeIds on the workspace or job) at /volumes/shared/<name>/{code,data}. When a volume's owner takes it away from a user (unshares it, or makes it private), it is dropped from that user's workspaces and jobs, and the user's reads and writes of it are refused at once. A workspace's data shows each file's latest version as of the workspace's start and its last Sync; new versions saved elsewhere appear at its next Sync or start. A job run or an app opens data at the latest version of each file.
A volume's scope is local (belongs to a project) or shared (usable across projects). A volume's name is unique among its owner's volumes of the same scope; a project's volume takes the project's name, which is unique among the organization's projects and volumes.
All endpoints require authentication via X-API-Key header and the appropriate scope.
Volume Object
{
"_id": "vol_def001",
"name": "training-data",
"scope": "local",
"projectId": "proj_abc123",
"organizationId": "org_xyz",
"owner": "user_456",
"description": "Dataset and code for ML model training",
"code": {
"filesystemType": "github",
"repoUrl": "git@github.com:my-org/training.git",
"branch": "main",
"initialized": false
},
"data": {
"latestVersion": 0,
"lastVersionAt": "2025-02-01T14:22:00Z"
},
"isShared": false,
"sharedWith": [
{ "userId": "user_789" }
],
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z"
}
A shared volume has no code. The code.filesystemType is either strongly (a platform-hosted git repository) or github (backed by an external GitHub repository, with repoUrl, optional branch, and an optional sshKeyId for a private repo). code.initialized is true once a strongly volume's repository has been created. projectId is set only on local volumes. sharedWith (read and write) and sharedReaders (read only) list the users the volume is shared with as { userId, role? } entries; they are absent until the volume is shared.
The data half is versioned per file, so there is no volume-wide data version number (data.latestVersion stays 0); data.lastVersionAt is when a file in it last changed. List its files with the data files endpoint to see each file's current version.
GET /api/v1/volumes
List volumes accessible to the authenticated user.
Scope: volumes:read
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
scope | string | No | Filter by scope: local or shared |
projectId | string | No | Filter by project ID |
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": "vol_def001",
"name": "training-data",
"scope": "local",
"projectId": "proj_abc123",
"organizationId": "org_xyz",
"owner": "user_456",
"description": "Dataset and code for ML model training",
"code": {
"filesystemType": "github",
"repoUrl": "git@github.com:my-org/training.git",
"branch": "main",
"initialized": false
},
"data": {
"latestVersion": 0,
"lastVersionAt": "2025-02-01T14:22:00Z"
},
"isShared": false,
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z"
}
],
"meta": {
"total": 6,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}
GET /api/v1/volumes/available-shared
The shared volumes you may choose for a workspace or job, the ids you pass as its sharedVolumeIds: volumes created as shared, and other projects' volumes their owners shared, that you own, were granted (read & write or read only), or that are shared to all users (on a multi-tenant platform, all users of your organization). Paged, as the workspace's Volumes tab and the create forms list them.
Scope: volumes:read
| Query parameter | Type | Description |
|---|---|---|
projectId | string | The project the workspace or job is in: its own volume is left out (it always mounts). An unknown project is 404 |
q | string | Name contains |
sort | string | name (default), createdAt or updatedAt |
sortDir | string | asc (default) or desc |
page | number | Page, from 1 |
pageSize | number | 1 to 100 (default 10) |
{
"total": 7,
"page": 1,
"pageSize": 10,
"items": [
{
"_id": "zxgy6r6nxmffc3kxs",
"name": "team-reference",
"mountPath": "/volumes/shared/team-reference",
"ownerName": "Strongly Demo",
"access": "read-only",
"isProjectVolume": false
}
]
}
access is owner, read-write or read-only. Choose volumes with sharedVolumeIds when you create or update a workspace (/api/v1/workspaces) or job (/api/v1/jobs).
POST /api/v1/volumes
Create a new volume. The usual one to create is a shared volume, which holds data only: send no code (it is refused with 400). A project's own volume, with its code, is created with its project; a local volume needs code.
Scope: volumes:write
Request Body
{
"name": "training-data",
"scope": "shared",
"description": "Training dataset"
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Volume name, unique among your volumes of the same scope |
scope | string | No | shared for a data-only shared volume, or local (the default) for a project's volume |
projectId | string | No | Project the volume belongs to (for local scope) |
description | string | No | Volume description |
code | object | For local | A project volume's code half (see below); refused for shared |
code object
| Field | Type | Required | Description |
|---|---|---|---|
filesystemType | string | Yes | strongly (platform-hosted git repo) or github |
repoUrl | string | For github | SSH URL of the GitHub repository |
branch | string | No | Branch to track (default main) |
sshKeyId | string | No | Reference to an SSH key for a private GitHub repo |
Response 201 Created
{
"data": {
"_id": "vol_def001",
"name": "training-data",
"scope": "local"
},
"meta": {
"requestId": "req_abc123"
}
}
GET /api/v1/volumes/:id
Get a single volume by ID.
Scope: volumes:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Response 200 OK
Returns the full Volume object.
GET /api/v1/volumes/:id/data/files
List the files in the volume's data half. Each entry carries that file's current (head) version, so different files can be at different versions.
Scope: volumes:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Response 200 OK
{
"data": {
"volumeId": "vol_def001",
"files": [
{
"path": "raw/customers.csv",
"name": "customers.csv",
"size": 45000,
"version": 3,
"updatedAt": "2025-02-01T14:22:00Z",
"updatedBy": "user_456"
},
{
"path": "models/model.pkl",
"name": "model.pkl",
"size": 10485760,
"version": 1,
"updatedAt": "2025-01-20T09:10:00Z",
"updatedBy": "user_456"
}
]
},
"meta": {
"requestId": "req_abc123"
}
}
| Field | Type | Description |
|---|---|---|
path | string | File path within the data half |
name | string | File name |
size | integer | File size in bytes |
version | integer | That file's current (head) version |
updatedAt | string | When the file was last written |
updatedBy | string | ID of the user who last wrote the file |
GET /api/v1/volumes/:id/data/files/versions
List the version history of a single file in the volume's data half, newest first. Each write of that file created a version.
Scope: volumes:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | File path within the data half |
Response 200 OK
{
"data": {
"path": "raw/customers.csv",
"versions": [
{
"version": 3,
"size": 45000,
"createdAt": "2025-02-01T14:22:00Z",
"createdBy": "user_456",
"createdByEmail": "ana@my-org.com",
"createdByName": "Ana Ruiz",
"message": "refreshed February export",
"deleted": false
},
{
"version": 2,
"size": 44200,
"createdAt": "2025-01-25T11:05:00Z",
"createdBy": "user_456",
"createdByEmail": "ana@my-org.com",
"createdByName": "Ana Ruiz",
"message": "",
"deleted": false
}
]
},
"meta": {
"requestId": "req_abc123"
}
}
| Field | Type | Description |
|---|---|---|
path | string | File path within the data half |
version | integer | Version number of this entry |
size | integer | File size in bytes at this version |
createdAt | string | When this version was written |
createdBy | string | ID of the user who wrote this version |
createdByEmail | string | Email of the user who wrote this version |
createdByName | string | Name of the user who wrote this version |
message | string | Optional message recorded with the write |
deleted | boolean | Whether this version is a deletion (a tombstone) |
GET /api/v1/volumes/:id/data/files/content
Download the bytes of a single file in the volume's data half, base64-encoded. Returns the file's latest version by default, or a specific per-file version with version.
Scope: volumes:read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | File path within the data half |
version | integer or string | No | Version to read. Default latest |
Response 200 OK
{
"data": {
"volumeId": "vol_def001",
"path": "raw/customers.csv",
"version": "latest",
"base64": "aWQsbmFtZQoxLEFuYQo=",
"sizeBytes": 14
},
"meta": {
"requestId": "req_abc123"
}
}
| Field | Type | Description |
|---|---|---|
volumeId | string | Volume ID |
path | string | File path within the data half |
version | string | The version you asked for, as a string (latest when omitted) |
base64 | string | The file's bytes, base64-encoded |
sizeBytes | integer | File size in bytes |
POST /api/v1/volumes/:id/data/files
Write a file to the volume's data half, creating a new version of that file. Send content for text or contentBase64 for binary bytes (exactly one).
Scope: volumes:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Request Body
{
"path": "raw/customers.csv",
"content": "id,name\n1,Ana\n",
"message": "refreshed February export"
}
| Field | Type | Required | Description |
|---|---|---|---|
path | string | Yes | File path within the data half |
content | string | One of | Text content to write |
contentBase64 | string | One of | Binary content to write, base64-encoded |
message | string | No | Optional message recorded with the new version |
Provide exactly one of content or contentBase64.
Response 200 OK
{
"data": {
"path": "raw/customers.csv",
"version": 4,
"changed": true,
"deleted": false
},
"meta": {
"requestId": "req_abc123"
}
}
Returns the file's new version number and whether its content actually changed. Re-writing the exact same bytes is a no-op, so changed is false and the version does not advance.
DELETE /api/v1/volumes/:id/data/files
Delete a file from the volume's data half. The file is tombstoned, so its earlier versions stay in history.
Scope: volumes:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | Yes | File path within the data half |
message | string | No | Optional message recorded with the deletion |
Response 200 OK
{
"data": {
"path": "raw/customers.csv",
"version": 5,
"changed": true,
"deleted": true
},
"meta": {
"requestId": "req_abc123"
}
}
The deletion is recorded as a new version of the file, so it stays traceable in the file's history.
DELETE /api/v1/volumes/:id
Delete a volume. It is refused while something uses it (a running workspace, a job run, an app built from it, or, for a project's own volume, its project). This action is irreversible.
Scope: volumes:write
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Volume ID |
Response 204 No Content
GET /api/v1/volumes/: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.
Scope: volumes:read
Code half
A project volume's code, as its Code tab shows it. These work on a volume whose code is on the Strongly filesystem: a GitHub volume's code is on GitHub, and a shared volume created on its own has no code (400). A shared volume's code is read-only, so writes to it are refused (403).
| Endpoint | Scope | What it does |
|---|---|---|
GET /api/v1/volumes/:id/code/branches | volumes:read | Its branches and default branch |
POST /api/v1/volumes/:id/code/branches | volumes:write | Create a branch. Body: name, optional from (a branch or commit; default the default branch) |
GET /api/v1/volumes/:id/code/tree?ref=&path= | volumes:read | The files and folders of one folder, on a branch (default the default branch) |
GET /api/v1/volumes/:id/code/files/content?ref=&path= | volumes:read | One file's content at a branch or commit |
GET /api/v1/volumes/:id/code/log?ref=&path=&limit= | volumes:read | A branch's commits, newest first, optionally of one file or folder (default 50) |
GET /api/v1/volumes/:id/code/diff?head=&base=&path= | volumes:read | What changed: a commit against its parent, or head against base, optionally for one file |
PUT /api/v1/volumes/:id/code/files/content | volumes:write | Create or replace a file, committed as you. Body: path, content, optional branch and message |
DELETE /api/v1/volumes/:id/code/files?path=&branch=&message= | volumes:write | Delete a file, committed as you |
Workspaces see a change made here when they pull (git pull in code/).