Skip to main content

Workspaces

Create, manage, and control development workspaces. Workspaces are interactive computing environments (e.g., Jupyter, VS Code, RStudio) provisioned within a project.

All endpoints require authentication via X-API-Key header and the appropriate scope.


Workspace Object​

{
"_id": "ws_ghi789",
"name": "Training Environment",
"description": "GPU-enabled workspace for model training",
"projectId": "proj_abc123",
"branch": "main",
"status": "running",
"environment": {
"type": "jupyter",
"label": "Jupyter Lab",
"port": 8888
},
"resources": {
"cpu": "4",
"memory": "16GB",
"disk": "20GB",
"gpu": "1",
"gpuType": "nvidia-a10g",
"useSpot": true,
"spotFallback": true
},
"workspaceVolumeSize": "50GB",
"environmentVariables": { "EPOCHS": "10" },
"addons": [],
"dataSources": [],
"aiGateways": [],
"workflows": [],
"codingAssistants": ["claude-code"],
"codeSessionEnabled": false,
"cluster": {
"engine": "ray",
"coordinator": { "cpu": "2", "memory": "4GB" },
"worker": { "cpu": "2", "memory": "4GB" },
"workers": 2,
"status": "ready",
"connectAddress": "ray://<host>:10001",
"dashboardUrl": "https://.../dashboard"
},
"volumes": [
{
"volumeId": "vol_def001",
"name": "training-data",
"scope": "local",
"code": { "remote": "git@github.com:my-org/training.git", "branch": "main" },
"data": { "version": "latest" }
}
],
"organizationId": "org_xyz",
"owner": "user_456",
"url": "/api/workspace-proxy/ws_ghi789/lab",
"createdAt": "2025-01-16T11:00:00Z",
"updatedAt": "2025-02-01T08:00:00Z"
}
  • resources.disk is the container's ephemeral scratch; workspaceVolumeSize is the persistent /workspace volume.
  • environment.type is the IDE (jupyter, vscode, rstudio, custom) and environment.port the port it serves on (for a Custom IDE, the customPort you set); a Custom IDE also carries any declared proxyHeaders.
  • status is one of building, stopped, deploying, starting, running, stopping, error.
  • owner is the id of the user who created the workspace. url is the path the workspace opens at on your instance.
  • platformUpdatePending is true while the running workspace was deployed by an earlier platform version; POST /workspaces/:id/restart applies the update (it ends open notebooks, terminals and processes). Absent otherwise. See Platform Updates.
  • volumes lists the code/data volumes the workspace mounts, as they mount.
  • cluster is present only when a compute cluster is attached; once ready it exposes the connectAddress and dashboardUrl. See Compute Clusters.

GET /api/v1/workspaces​

List all workspaces accessible to the authenticated user.

Scope: workspaces:read

Query Parameters

ParameterTypeRequiredDescription
qstringNoSearch by name or description
statusstringNoFilter by status: building, stopped, deploying, starting, running, stopping, error
projectIdstringNoFilter by project ID
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": "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 },
"resources": { "cpu": "4", "memory": "16GB", "disk": "20GB" },
"organizationId": "org_xyz",
"owner": "user_456",
"url": "/api/workspace-proxy/ws_ghi789/lab",
"createdAt": "2025-01-16T11:00:00Z",
"updatedAt": "2025-02-01T08:00:00Z"
}
],
"meta": {
"total": 8,
"limit": 50,
"nextCursor": null,
"requestId": "req_abc123"
}
}

POST /api/v1/workspaces​

Create a new workspace.

Scope: workspaces:write

Request Body

The request body has full parity with the Create Workspace UI: choose the IDE, size it (with custom resources or a saved environment), set a persistent volume size, attach services, environment variables, coding assistants, and optionally a distributed compute cluster.

{
"name": "Training Environment",
"description": "GPU-enabled workspace for model training",
"environmentType": "jupyter",
"projectId": "proj_abc123",
"customResources": {
"cpu": "4",
"memory": "16GB",
"disk": "20GB",
"gpu": "1",
"gpuType": "nvidia-a10g"
},
"workspaceVolumeSize": "50GB",
"useSpot": false,
"environmentVariables": { "EPOCHS": "10" },
"dataSources": [],
"addons": [],
"aiGateways": [],
"workflows": [],
"codingAssistants": ["claude-code"],
"cluster": {
"engine": "ray",
"coordinator": { "cpu": "2", "memory": "4GB" },
"worker": { "cpu": "2", "memory": "4GB" },
"workers": 2,
"engineOptions": { "metricsDashboards": true }
}
}
FieldTypeRequiredDescription
namestringYesWorkspace name
descriptionstringYesWorkspace description
environmentTypestringYesThe IDE the workspace opens: jupyter, vscode, rstudio, or custom. Independent of the hardware sizing.
projectIdstringNoProject the workspace belongs to. The project's volume is mounted at /volumes/local/<name>. If omitted, a project is created automatically.
sharedVolumeIdsstring[]NoThe shared volumes to mount at /volumes/shared/<name> (code/ read-only). None unless listed. A volume you may not mount is refused with 400, by name, and one whose share is later taken away is dropped from the list.
environmentIdstringNoId of a saved environment (from list_environments) supplying the container image and the size. Use this for a specific software stack (for example an RStudio image with your R packages). Set either environmentId or customResources.
environmentVersionnumberNoPin a specific version of the selected environment. Omit for the latest.
customResourcesobjectWhen no environmentIdThe size when not using environmentId: { cpu, memory, disk, gpu?, gpu_type? }. The workspace runs at exactly this size; no size is assumed, and a create with neither customResources nor environmentId is refused. At least 0.5 CPU and 1 GB of memory (the platform services' share plus room for the IDE); a smaller workspace is refused at Start with the size that would do. disk is the container's ephemeral scratch disk (not the persistent volume). A size with gpu is checked against the GPU machine sizes allowed on the Compute page and refused with a message naming the machine it needs when none is allowed (an A100 or H100 needs the largest sizes).
workspaceVolumeSizestringNoSize of the persistent /workspace scratch volume (virtual environments, caches, scratch files), which survives stop/start and is deleted with the workspace; until you sync, the working copy of the code and the data files you changed are kept on it too (for example "50GB"). Code and data you keep belong in the project's volume. Distinct from customResources.disk. Default "20GB".
useSpotbooleanNoRun the workspace on spot capacity (cheaper; it can be reclaimed, and the workspace restarts on a new node with its volumes kept). Default false.
spotFallbackbooleanNoWith useSpot, fall back to on-demand capacity when no spot is available (default true); false waits for spot.
customPortnumberNoCustom IDE only: the port the environment image serves its IDE on. Default 8888.
proxyHeadersarrayNoCustom IDE only: extra request headers forwarded to your IDE, as { name, value }. Values may use ${BASE_PATH} / ${ORIGIN} / ${PATH} / ${REQUEST_URI} placeholders.
environmentVariablesobjectNoKey/value environment variables injected into the workspace container.
dataSourcesarrayNoIds of data sources to wire into the workspace (injected via STRONGLY_DATA_SOURCES).
addonsarrayNoIds of add-ons to attach (injected via STRONGLY_SERVICES).
aiGatewaysarrayNoIds of AI models / gateways to make available (injected via STRONGLY_SERVICES).
workflowsarrayNoIds of workflows to make available (injected via STRONGLY_SERVICES).
codingAssistantsarrayNoThe coding-assistant CLI to install at startup: at most one of claude-code, codex, opencode (more than one is refused with 400). Available for Jupyter and VS Code only.
skillIdsarrayNoIds of your Skill Library skills to install at each start, with the Strongly skill, for every coding assistant (chosen or not). Jupyter Lab and VS Code only; a skill you cannot read is refused with 400.
codeSessionEnabledbooleanNoLet an agent drive a terminal in the workspace (a code session).
clusterobjectNoAttach a distributed compute cluster. engine is one of ray, dask, spark; plus coordinator: { cpu, memory }, worker: { cpu, memory, gpu?, gpuType? }, workers, optional autoscale: { enabled, maxWorkers }, and engineOptions: { metricsDashboards }. The cluster is provisioned on deploy, removed on stop, recreated on start, and deleted with the workspace. See Compute Clusters.

Response 201 Created

The workspace is created stopped; start it to run it.

{
"data": {
"success": true,
"workspaceId": "ws_ghi789",
"buildInfo": {
"buildId": "b_123",
"workspaceId": "ws_ghi789",
"workspaceName": "Training Environment",
"status": "pending",
"progress": 0,
"logs": ["Starting workspace creation...", "Resources (Custom): CPU 4, Memory 16GB, Disk 20GB"],
"createdAt": "2025-01-16T11:00:00Z"
}
},
"meta": {
"requestId": "req_abc123"
}
}

Error 400 Bad Request when neither environmentId nor customResources is set, or the size is too small:

{
"type": "urn:strongly:problem:validation-error",
"title": "Validation failed",
"status": 400,
"detail": "Set the workspace CPU, memory and disk (or choose an environment)",
"code": "validation-error",
"requestId": "req_abc123"
}

GET /api/v1/workspaces/:id​

Get a single workspace by ID.

Scope: workspaces:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

Returns the full Workspace object.


PATCH /api/v1/workspaces/:id​

Rename a workspace, change its description, change the services it selects or its coding assistants and their skills (applied at its next start or restart), or set its environment variables. Send only the fields you are changing; any other field is refused with 400. The size, image, IDE and environment are fixed when the workspace is created; to change them, create a new workspace.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Request Body

{
"name": "Updated Training Environment",
"description": "Updated description",
"environmentVariables": { "EPOCHS": "10" }
}
FieldTypeRequiredDescription
namestringNoWorkspace name
descriptionstringNoWorkspace description
addons, dataSources, aiGateways, mlModels, workflows, featureStores, agentsarrayNoThe services the workspace selects, each list replacing the current one (its STRONGLY_SERVICES is built from them)
codingAssistantsarrayNoThe coding-assistant CLI installed at each start, at most one of claude-code, codex, opencode ([] for none), replacing the current one. More than one is refused with 400. Jupyter and VS Code only.
skillIdsarrayNoSkill Library skills installed at each start for every coding assistant (chosen or not), replacing the current list. Jupyter Lab and VS Code only; a skill the workspace's owner cannot read is refused with 400. A selected skill that was deleted or is no longer shared stops the start, so remove it here.
sharedVolumeIdsstring[]NoThe shared volumes it mounts at /volumes/shared/<name> from its next start or restart, replacing the current list ([] for none). The project's volume always mounts. A volume the owner may not mount is refused with 400, by name.
environmentVariablesobjectNoEnvironment variables (string values), replacing the current set. They are applied when the workspace is deployed: its first start, or a start from error.

Response 200 OK

{
"data": {
"success": true
},
"meta": {
"requestId": "req_abc123"
}
}

Errors

StatusCodeDescription
400validation-errorThe body has a field other than the three above (for example cpu or image), or an environment variable value is not a string
409invalid-stateenvironmentVariables on a workspace that is already deployed

DELETE /api/v1/workspaces/:id​

Delete a workspace, running or stopped: a running one is stopped first. Its /workspace volume and anything not synced to its volumes are deleted with it. This action is irreversible.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 204 No Content


POST /api/v1/workspaces/:id/start​

Start a stopped workspace. The call returns as soon as the start is accepted, with the workspace in starting; it does not wait for the IDE to come up. Poll GET /api/v1/workspaces/:id until status is running (the workspace url is then set) or error. The first start of a new workspace deploys it. Starting a workspace that is already running or starting changes nothing and returns 200.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

{
"data": {
"success": true,
"message": "Workspace is starting"
},
"meta": {
"requestId": "req_abc123"
}
}

POST /api/v1/workspaces/:id/stop​

Stop a running workspace. Releases compute resources while preserving workspace state.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

{
"data": {
"success": true,
"message": "Workspace stopped successfully. Data in volumes has been preserved."
},
"meta": {
"requestId": "req_abc123"
}
}

POST /api/v1/workspaces/:id/restart​

Restart a running workspace. Equivalent to stop followed by start.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

{
"data": {
"success": true
},
"meta": {
"requestId": "req_abc123"
}
}

GET /api/v1/workspaces/:id/status​

Read the workspace's current status now. The platform records what it finds, so the workspace list and details page show the same status.

Scope: workspaces:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

{
"data": {
"status": "running",
"url": "/api/workspace-proxy/45TwJjGz6v9FWnAt3",
"error": null
},
"meta": {
"requestId": "req_abc123"
}
}
FieldDescription
statusdeploying, starting, running, stopping, stopped or error.
urlWhere the workspace IDE opens once it is running.
errorWhy the workspace is in error, for example a container that keeps crashing, an image that cannot be pulled, or a setup step that failed; null otherwise.

A workspace is in error only when it has actually failed. A slow start stays starting for as long as it takes; there is no time limit that turns it into an error.

Errors

StatusCodeWhen
404not-foundNo workspace with this ID that you can see.
502backend-unavailableThe status could not be read right now. The workspace's status is left unchanged; try again.

GET /api/v1/workspaces/:id/metrics​

Measure a running workspace now. Each call takes a fresh measurement (about 6 seconds, including a 5 second network sample). Nothing is cached, and a value that cannot be measured is reported as an error rather than a zero.

Scope: workspaces:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

{
"data": {
"cpu": { "usageMillicores": 250.4, "limitMillicores": 1000, "percent": 25.0 },
"memory": { "usageBytes": 1073741824, "limitBytes": 2147483648, "percent": 50.0 },
"disk": { "usedBytes": 2092804096, "capacityBytes": 10464022528, "percent": 20.0, "path": "/workspace" },
"network": { "receiveBytesPerSecond": 5125.4, "transmitBytesPerSecond": 1500.2, "totalBytesPerSecond": 6625.6, "windowSeconds": 5.02 },
"gpu": null,
"responseTime": { "avgMs": 3.1, "minMs": 2.4, "maxMs": 4.9, "samples": 5, "urlPath": "/" },
"containers": [
{ "name": "workspace", "role": "workspace", "cpuMillicores": 250.4, "cpuLimitMillicores": 1000, "cpuPercent": 25.0,
"memoryBytes": 1073741824, "memoryLimitBytes": 2147483648, "memoryPercent": 50.0 },
{ "name": "vol-data", "role": "platform", "cpuMillicores": 1.2, "cpuLimitMillicores": 100, "cpuPercent": 1.2,
"memoryBytes": 20971520, "memoryLimitBytes": 268435456, "memoryPercent": 7.8 }
],
"platformContainersTotal": { "cpuMillicores": 1.2, "memoryBytes": 20971520, "count": 1 },
"measuredAt": "2026-09-23T04:03:40.331446+00:00"
},
"meta": {
"requestId": "req_abc123"
}
}
FieldDescription
cpu, memoryUsage of your workspace container against the size you set.
diskSpace used on the workspace volume mounted at /workspace.
networkBytes per second received and sent over the sampling window, so even light traffic shows a reading.
gpu{ "percent": ... } GPU utilization for workspaces with GPUs; null otherwise.
responseTimeRound trip time of the workspace IDE answering an HTTP request.
containersEvery container in the workspace, with its usage and size. role is workspace for yours and platform for the containers the platform runs beside it, such as one per mounted data volume.
platformContainersTotalCombined usage of the platform containers.

Errors

StatusCodeWhen
409invalid-stateThe workspace is not running.
500internal-errorA measurement could not be taken; the message gives the reason.

GET /api/v1/workspaces/:id/logs​

Retrieve logs for a workspace. Supports different log types for build, deploy, and runtime phases.

Scope: workspaces:read

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Query Parameters

ParameterTypeRequiredDescription
typestringNoWhich log: build, deploy, or pod (the running IDE's own log, the Runtime log on the Logs tab; the default)

Response 200 OK

{
"data": [
{
"timestamp": "2025-02-01T08:01:30Z",
"level": "info",
"message": "Starting Jupyter server..."
},
{
"timestamp": "2025-02-01T08:01:32Z",
"level": "info",
"message": "Jupyter server is running at https://0.0.0.0:8888"
}
],
"meta": {
"requestId": "req_abc123"
}
}

POST /api/v1/workspaces/:id/sync​

Sync the running workspace to its durable volumes. For the project's volume, this commits and pushes the code half to its configured branch; for every mounted volume you may write, it records the data files you changed, each as a new version of that file. A shared volume's code is read-only, so its code result is { "skipped": "read-only" }. Unsynced work survives stop, start and restart; only deleting the workspace loses it. Until you sync, it is not on the volume, not visible to others, not versioned, and not what an app deploy builds from. Sync before you delete the workspace, hand work off, or deploy from the volume.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Response 200 OK

{
"data": {
"workspaceId": "ws_ghi789",
"synced": [
{
"volumeId": "vol_def001",
"name": "training-data",
"result": {
"volumeId": "vol_def001",
"base": "/volumes/local/training-data",
"code": { "committed": true, "pushed": true, "conflict": false, "rebased": false },
"data": { "committed": true, "changed": 1, "files": { "forecasts/weekly.csv": { "path": "forecasts/weekly.csv", "version": 3 } }, "refreshed": true }
}
},
{
"volumeId": "vol_sh002",
"name": "datasets",
"result": {
"volumeId": "vol_sh002",
"base": "/volumes/shared/datasets",
"code": { "skipped": "read-only" },
"data": { "committed": false, "reason": "no data changes", "refreshed": true }
}
}
]
},
"meta": {
"requestId": "req_abc123"
}
}

data.changed counts the files saved as new versions. refreshed means the workspace's data/ now shows every file's latest version; when that could not be done, refreshError says why (restart the workspace to see them).

With no volumes mounted, data is { "synced": [], "message": "This workspace mounts no code/data volumes" }.

When the code changed on the volume since the workspace's last sync, on the same lines, the volume's code result is { "conflict": true, "pushed": false, "files": ["app.py"] }: nothing is overwritten, and the merge waits for you to decide each file (below). Sync again once no files are left: that finishes the merge and pushes it.


GET /api/v1/workspaces/:id/sync/conflicts​

The files still to decide after a Sync conflict, per mounted volume. A file you merged by hand in the IDE counts as decided once its conflict markers are gone.

Scope: workspaces:read

Response

{
"data": {
"workspaceId": "ws_abc123",
"volumes": [
{ "volumeId": "vol_def001", "name": "training-data", "merging": true, "files": ["app.py"] }
]
}
}

POST /api/v1/workspaces/:id/sync/resolve​

Decide one conflicted file: keep your version or take the volume's.

Scope: workspaces:write

Request Body

FieldTypeRequiredDescription
volumeIdstringYesThe mounted volume the file is in
pathstringYesThe file, relative to the volume's code/ folder
takestringYesmine (this workspace's version) or theirs (the volume's)

Returns the volume's remaining files, as GET .../sync/conflicts does.


POST /api/v1/workspaces/:id/sync/abort​

Cancel a volume's waiting merge: the workspace's code goes back to how it was before the Sync (your own changes kept, not yet synced).

Scope: workspaces:write

Request Body

FieldTypeRequiredDescription
volumeIdstringYesThe mounted volume

POST /api/v1/workspaces/:id/execute​

Run a shell command inside the running workspace container and return its output. The workspace must be running.

Scope: workspaces:write

Path Parameters

ParameterTypeRequiredDescription
idstringYesWorkspace ID

Request Body

{
"command": "python train.py --epochs 10",
"timeoutSec": 120,
"cwd": "/volumes/local/my-project/code"
}
FieldTypeRequiredDescription
commandstringYesThe shell command to run
timeoutSecnumberNoKill the command after this many seconds
cwdstringNoWorking directory to run the command in

Response 200 OK

{
"data": {
"stdout": "Epoch 10 complete\n",
"stderr": "",
"exitCode": 0,
"timedOut": false
},
"meta": {
"requestId": "req_abc123"
}
}

On failure, stderr carries the error output and exitCode is non-zero. timedOut is true if the command exceeded timeoutSec.


GET /api/v1/workspaces/minimum-size​

The smallest workspace that runs: cpu and memoryGb (the platform's services beside the IDE, in platform, plus room for it), the smallest compute-cluster coordinator per engine (clusterCoordinator), and the smallest job run (job). A smaller size is refused at create and at start.

Scope: workspaces:read


GET /api/v1/workspaces/planned-volumes​

The volumes a new workspace in a project would mount: { volumes: [{ volumeId, name, scope }] }, the project's volume (scope local) and the shared volumes listed in the sharedVolumeIds query parameter, comma-separated (scope shared; none if omitted). A listed volume you may not mount is refused with 400, by name.

Scope: workspaces:read

Query parameterTypeDescription
projectIdstringThe project the workspace would be in

GET /api/v1/workspaces/: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: workspaces:read


GET /api/v1/workspaces/:id/volumes​

The volumes the workspace mounts, as its Volumes tab lists them, each with its mount path, code source, owner and access. Paged.

Scope: workspaces:read

Query parameterTypeDescription
groupstringproject (default): its project's volume. shared: the shared volumes chosen for it
qstringName contains
sortstringname (default), createdAt or updatedAt
sortDirstringasc (default) or desc
pagenumberPage, from 1
pageSizenumber1 to 100 (default 10)

POST /api/v1/workspaces/:id/ports​

Save a labeled quick link to a port (the Ports tab). Any port from 1024 to 65535 opens at <workspace URL>/port/<port>/ with or without a saved link; the link is a bookmark. Only the workspace's owner can open its ports.

Scope: workspaces:write

FieldTypeRequiredDescription
portnumberYesPort the app listens on (1024 to 65535)
labelstringNoLabel for the link

Response 201 Created: the link, { port, label, url }.


DELETE /api/v1/workspaces/:workspaceId/ports/:id​

Remove a saved port link. The app on that port keeps running and still opens by its port URL.

Scope: workspaces:write