--- title: "DevPlace REST: Dev Workspaces and Tunnels" description: "Workspace lifecycle endpoints for the browser editor attached to a project, the editor-preferences reads and writes, and tunnel creation that publishes a container port on a public HTTPS hostname, with quota and idle-reaping behaviour." language: null framework: null category: api_design tags: - devplace - api - http-api - workspaces - editor - tunnels - rest - quotas - containers - port-forwarding keywords: - devplace workspace start stop endpoint - devplace workspace editor preferences api - devplace tunnel publish port public hostname - devplace workspace disk egress quota idle reaping last_updated: 2026-08-12 difficulty: advanced version: "DevPlace (devplace.net), documented 2026-08" related: - README.md - authentication.md - conventions_and_errors.md search_priority: normal status: published --- # DevPlace REST: Dev Workspaces and Tunnels A workspace is a browser editor attached to one of your projects. It runs your project files, a terminal, and preinstalled Python, Rust, Nim and Swift toolchains. `sudo` and `apt install` work with no extra setup; ports below 1024 cannot bind, so use a high port and publish it through a tunnel. The editor opens with a **DevPlace Code** terminal already running the `dpc` coding agent and a plain shell beside it, and it trusts every folder, so nothing opens in Restricted Mode. Its appearance and boot behaviour are your own preferences, readable and writable through the two `/workspace/editor` endpoints below and explained on [the workspace editor page](https://devplace.net/docs/#doc-workspace-editor). Editor preferences apply on the next workspace start. A **tunnel** publishes one port from inside your container on a public HTTPS hostname of the form `-.tunnel.pravda.education`. **Tunnel URLs are public and unauthenticated** - anyone with the link can reach whatever you are serving. Forwarding a port in the editor's **Ports** view creates the tunnel for you through the same endpoint; un-forwarding it does not remove the tunnel. Workspaces are bounded: a count limit per user, a disk quota, an egress quota, and a tunnel limit. An idle workspace is warned about, then stopped, then warned again, then removed. Every warning arrives as a `workspace` notification and states exactly what happens next and when. ## `GET /projects/{slug}/workspace` - Read workspace State, quota usage, idle countdown, tunnels and open moderation flags for your workspace on this project. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | **Sample response** ```json { "has_workspace": true, "viewer_can_workspace": true, "workspace_count": 1, "max_workspaces": 2, "editor_url": "/projects/my-project/containers/instances/INSTANCE_UID/code/", "workspace": { "uid": "INSTANCE_UID", "status": "running", "suspended": false, "tunnel_name": "brave-otter", "primary_url": "https://brave-otter.tunnel.pravda.education", "disk_bytes": 5242880, "disk_quota_mb": 2048, "disk_percent": 1, "egress_bytes": 10240, "egress_quota_mb": 10240, "egress_percent": 0, "idle_stop_minutes": 60, "retention_days": 14, "max_tunnels": 5, "tunnels": [], "flags": [] } } ``` ## `POST /projects/{slug}/workspace` - Open or resume workspace Create the workspace if you have none for this project, otherwise resume it. Idempotent. Refused when you are at your workspace limit, over disk quota, or suspended. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | **Sample response** ```json { "ok": true, "redirect": "/projects/my-project/workspace" } ``` ## `POST /projects/{slug}/workspace/stop` - Stop workspace Stop the container. Files and tunnels are kept. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | **Sample response** ```json { "ok": true, "redirect": "/projects/my-project/workspace" } ``` ## `POST /projects/{slug}/workspace/delete` - Delete workspace Remove the workspace and its tunnels. An administrator can restore it. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | **Sample response** ```json { "ok": true, "redirect": "/projects/my-project/workspace" } ``` ## `GET /projects/{slug}/workspace/editor` - Read editor profile The resolved DevPlace editor profile for this workspace: theme, layout, panel preset, font sizes, zoom, boot terminals, how the editor opens, the container size, and where each value comes from. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | **Sample response** ```json { "editor": { "trust_all": true, "theme": "devplace-dark", "font_size": 14, "terminal_font_size": 13, "zoom_level": 0, "layout": "standard", "panel_preset": "tall", "boot_agent": "dpc", "boot_shell": true, "window_mode": "tab", "window_width": 1600, "window_height": 1000, "cpu_millicores": 2000, "cpu_cores": 2.0, "memory_mb": 2048, "disk_quota_mb": 2048, "sources": { "theme": "user", "font_size": "site" } }, "restart_required": false } ``` ## `POST /projects/{slug}/workspace/editor` - Set editor preferences Change your own editor preferences. Only the fields you send are changed; within those, an empty string or zero means inherit the site default, and `reset` drops every preference. Applies on the next workspace start, and the response says whether a restart is needed. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | | `theme` | body | string | no | devplace-dark, devplace-light or system. | | `layout` | body | string | no | standard, terminal-focus or zen. | | `panel_preset` | body | string | no | short, normal, tall or maximized. | | `font_size` | body | integer | no | Editor font size in pixels. Zero inherits. | | `terminal_font_size` | body | integer | no | Terminal font size in pixels. Zero inherits. | | `zoom_level` | body | integer | no | Window zoom, -5 to 5. Send -99 to inherit. | | `boot_agent` | body | string | no | dpc or none. | | `boot_shell` | body | integer | no | 1 opens a shell on boot, 0 skips it, -1 inherits. | | `window_mode` | body | string | no | tab, window or fullscreen. | | `window_width` | body | integer | no | Editor window width in pixels. Zero inherits. | | `window_height` | body | integer | no | Editor window height in pixels. Zero inherits. | | `reset` | body | boolean | no | Drop every preference and fall back to the site defaults. | **Sample response** ```json { "ok": true, "redirect": "/projects/my-project/workspace", "data": { "restart_required": true } } ``` ## `GET /projects/{slug}/workspace/tunnels` - List tunnels Every public tunnel published by this workspace. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | **Sample response** ```json { "tunnels": [ { "uid": "TUNNEL_UID", "hostname": "8080-brave-otter.tunnel.pravda.education", "label": "web", "container_port": 8080, "status": "active", "cert_status": "valid", "request_count": 12, "bytes_out": 40960 } ] } ``` ## `POST /projects/{slug}/workspace/tunnels` - Create tunnel Publish a container port on a public HTTPS hostname. The URL is public and unauthenticated. Refused past the tunnel limit. The certificate is ordered right away, so the hostname answers plain HTTP for a few seconds before it serves HTTPS. Forwarding a port in the editor calls this for you. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | | `container_port` | body | integer | yes | Port inside the container. | | `label` | body | string | no | Human label. | **Sample response** ```json { "ok": true, "data": { "uid": "TUNNEL_UID", "hostname": "8080-brave-otter.tunnel.pravda.education", "status": "pending" } } ``` ## `POST /projects/{slug}/workspace/tunnels/{uid}/delete` - Delete tunnel Remove a tunnel. The public URL stops serving immediately. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `slug` | path | string | yes | Project slug or uid. | | `uid` | path | string | yes | Tunnel uid. | **Sample response** ```json { "ok": true, "redirect": "/projects/my-project/workspace" } ```