Skip to content
DocsAll docs
Console

Desks API

Lists the API routes that create desks, create and delete their workspaces, set their environment, and end them.

Last updated

On this page

The desk document

json
{  "id": "desk-9c8b7a6d5e4f3a2b",  "name": "login-fix",  "owner": "karthik",  "team": "acme",  "environment": "env-1f2e3d4c5b6a7988",  "workspaces": [{ "id": "ws-0a1b2c3d4e5f6a7b", "name": "backend", "state": "running" }],  "intercepts": [{ "service": "api", "workspace": "ws-0a1b2c3d4e5f6a7b", "ports": [{ "service": 8080, "workspace": 3000 }], "desk": "desk-9c8b7a6d5e4f3a2b" }],  "ttl_secs": 604800,  "expires_at": "2026-10-25T09:30:00Z",  "state": "running"}
FieldTypeMeaning
idstringdesk- and 16 hex digits
namestringthe name you gave it; unique among your desks in that team
ownerstringyour handle, lower-cased
teamstringthe team slug. A personal desk carries your own handle here, not ""
environmentstring or nullthe id of the environment it is connected to
workspacesobject[], omittedthe workspaces it manages: id, name, and state (running or stopped, the state you asked for).
interceptsobject[], omittedthe intercepts this desk holds in its environment, in the form the environment document uses. Empty with no environment
ttl_secsinteger, omittedunused seconds before it is deleted
expires_atstring, omittedRFC 3339; last active (else created) plus ttl_secs
statestringstopped after a stop; idle when unused past the idle limit; else running. Present in the list too
reasonstring, omittedwhy the desk is degraded: N messages failed; see the queue when the runtime gave up on messages. Absent when healthy

workspaces and intercepts are present on every route that returns one desk, and absent from the list.

POST /v1/desks

Creates a desk, optionally connected to an environment from birth.

FieldTypeRequiredRule
namestringyesThe workspace name rule: 1 to 40 lowercase letters, digits, or -; starts with a letter, ends with a letter or digit, no --; not ws, env, api, admin, system, kd, or bld-…, and not shaped like an id. Unique among your desks in that team
teamstringnoA team you belong to. Absent, empty, or your own handle means a personal desk. Case is ignored
environmentstringnoAn environment (id, name, or owner/name) of that same team. Absent or empty means none yet
ttl_secsintegerno600 to 2592000. Absent means never
json
{ "name": "login-fix", "team": "acme", "environment": "acme-dev", "ttl_secs": 604800 }

Response: 201, the desk document.

Errors:

  • 403 — a desk tool token naming a team that is not its own.
  • 404 — a team you are not a member of; the environment is not found or not yours.
  • 409 — you already have a desk named login-fix in acme; the environment belongs to another owner (that environment is {owner}'s; a desk in {team} connects only to {team}'s environments).
  • 422 — the name breaks the rule (JSON); ttl_secs must be 600..=2592000 seconds.

GET /v1/desks

Your desks, as an array of documents without workspaces and intercepts. ?team={slug} lists one team's; ?team= (empty) lists the personal ones; absent lists every team's.

Response: 200.

json
[  { "id": "desk-9c8b7a6d5e4f3a2b", "name": "login-fix", "owner": "karthik", "team": "acme", "environment": "env-1f2e3d4c5b6a7988", "ttl_secs": 604800, "expires_at": "2026-10-25T09:30:00Z" }]

GET /v1/desks/{id}

One desk. {id} is the id, a name, or team/name; ?owner={team} is the same as qualifying it. A bare name that matches a personal and a team desk prefers the personal one.

Response: 200, the desk document.

Errors:

  • 404 — not found, or not yours.
  • 409 — more than one of your desks has that name (more than one desk is named login-fix; name it as team/login-fix).

DELETE /v1/desks/{id}

Deletes the desk. Its managed workspaces are deleted (snapshots stay) and its capsules are ended (their run records stay). Its intercepts are released, then the desk is deleted. Takes no body.

Response: 200 { "deleted": ["ws-…", "cap-…"] }, the ids of the workspaces deleted and capsules ended. If a step fails, the call fails and the desk stays; the same call finishes the job.

Errors:

  • 404 — not found, or not yours; a pipeline token deleting a desk its own run did not make.
  • 409 — the environment was changed while this was being written; try again; the workspace was changed while this was being written; try again.

POST /v1/desks/{id}/open

Opens the desk: starts its stopped workspaces when start is set, wakes an idle one, and returns the tunnel token. The runtime that serves the desk is always up, so an open creates no machine.

Body, all optional: start (boolean; start a stopped desk and its stopped workspaces), model (string; used at creation) and env (object of variables; used at creation).

Response: 201 { "token", "gateway", "expires_at" }. The token is valid for 60 seconds and opens the /tui/ and /net/ doors of the gateway. 202 { "state": "waking" } only while the wake of an idle desk is in flight; call again.

Errors:

  • 403 — your access to {team} is paused.
  • 404 — not found, or not yours.
  • 409 — the desk is stopped; start it with start; a quota refusal for the start of a workspace.

POST /v1/desks/{id}/stop

Stops the desk and every workspace in it, through the ordinary workspace stop. A capsule and the connected environment are not touched. Takes no body.

Response: 202 { "stopped": [workspace ids] }.

Errors:

  • 403 — your access to {team} is paused.
  • 404 — not found, or not yours.

POST /v1/desks/{id}/start

Starts the connected environment first, through the ordinary environment start, even when it was stopped by hand. Then starts every workspace in the desk, through the ordinary workspace start, and wakes the desk. A desk with no environment skips the first step. Capsules are not touched.

Takes no body.

Response: 202 { "started": [workspace ids], "environment": id or null }. environment is null when the desk has none.

Errors:

  • 403 — your access to {team} is paused.
  • 404 — not found, or not yours.
  • 409 — the environment or a workspace refuses to start, with its own sentence, for example a quota refusal. When the environment refuses, no workspace starts. Workspaces before a refusing workspace stay started.

GET /v1/desks/teams

Lists the teams in which you can open a desk: your personal team first, then the teams you belong to. Takes no parameters.

Response: 200, an array of { "slug", "name", "region", "personal" }. region is "" when the team has none.

Errors:

  • 503 — team list unavailable.

POST /v1/desks/{id}/hold

Records that you are using the desk, and wakes it and its idle-stopped environment. A laptop connection calls this every 60 seconds while in use. Takes no body.

Response: 204 with an empty body.

Errors:

  • 404 — not found, or not yours.

GET /v1/desks/{id}/queue

Reads the messages waiting for the desk's agents in the runtime. Only the desk's owner gets it. Reading it never wakes or holds the desk.

Response: 200 with pending (messages not yet handled), oldest_age_secs (the age of the oldest one, or null with none) and failed. A message is given up on after five failed attempts over about 85 seconds.

Errors:

  • 404 — not found, or not yours.
  • 503 — the queue is unreadable; object store not configured.

POST /v1/desks/{id}/queue/replay

Moves the messages the runtime gave up on back to the queue, oldest first, so the desk's agents handle them again. Only the desk's owner gets it. Use it after the cause of the failures is gone. It sends no body.

Response: 204 with an empty body. The runtime picks the messages up within about two minutes.

Errors:

  • 404 — not found, or not yours.
  • 503 — the queue is unreadable; object store not configured.

PUT /v1/desks/{id}/ttl

Sets or clears how long the desk can stay unused before it is deleted. A desk that is awake is never deleted by its time to live.

FieldTypeRequiredRule
ttl_secsinteger or nullno600 to 2592000 to set; null or an absent key clears it
json
{ "ttl_secs": 86400 }

Response: 200, the desk document with ttl_secs and expires_at.

Errors:

  • 404 — not found, or not yours.
  • 422 — ttl_secs must be 600..=2592000 seconds.

PUT /v1/desks/{id}/environment

Connects the desk to an environment, switches it, or clears it. The intercepts the desk held in the old environment are released first. Connecting counts as use and wakes the desk and an idle-stopped environment.

FieldTypeRequiredRule
environmentstring or nullyesAn environment (id, name, or owner/name) of the desk's team. null or an empty string clears it
json
{ "environment": "acme-dev" }

Response: 200, the desk document. Asking for the environment it already has changes nothing.

Errors:

  • 404 — the desk or the environment is not found or not yours.
  • 409 — the environment belongs to another owner than the desk's team (that environment is {owner}'s; a desk in {team} connects only to {team}'s environments); the desk changed; retry.

POST /v1/desks/{id}/workspaces

Creates a workspace managed by the desk, and wakes an idle desk. The workspace is in the desk's team. From then on the desk controls it. Start, stop, delete, and ttl on the workspace itself are refused with 409 (see workspaces). The desk itself can call this route. Another desk gets 403.

The body is the POST /v1/workspaces body without desk. team is optional. If given, it must be the desk's team. With device, the workspace runs on that device of yours and joins the desk. See Device workspaces.

Response: 201, the workspace document, with desk set.

Errors:

  • 400 — team is not the desk's team; desk in the body.
  • 404 — the desk is not found or not yours.
  • 403 — the token of another desk.
  • The errors of POST /v1/workspaces: name, quota, region, and package refusals.
  • 409 — this desk has no environment; connect one with PUT /v1/desks/{id}/environment when the desk names an environment that no longer exists.

POST /v1/desks/{id}/capsules

Creates a capsule managed by the desk, and wakes an idle desk. The capsule is in the desk's team and reaches the desk's environment. From then on the desk controls it: POST /v1/capsules/{id}/terminate and DELETE /v1/capsules/{id} from any other caller are refused with 409 ({name} is managed by desk {desk}; use the desk), and deleting the desk ends the capsule. A standalone capsule is made with POST /v1/capsules, which does not take a desk.

The body is the POST /v1/capsules body without desk. team is optional. If given, it must be the desk's team.

Response: 202, the capsule document, with desk set.

Errors:

  • 400 — team is not the desk's team; desk in the body.
  • 404 — the desk is not found or not yours.
  • 403 — the token of another desk.
  • The errors of POST /v1/capsules: command, env, quota, region, and package refusals.
  • 409 — this desk has no environment; connect one with PUT /v1/desks/{id}/environment when the desk names an environment that no longer exists.

DELETE /v1/desks/{id}/workspaces/{workspace}

Deletes a workspace the desk manages, releasing its intercepts first, through the same path as DELETE /v1/workspaces/{id}. Snapshots stay. A capsule is ended, not deleted: its run record stays. Takes no body.

Response: 204 with an empty body.

Errors:

  • 404 — the desk or the workspace is not found or not yours; the workspace is not managed by this desk.
  • 403 — the token of another desk.
  • 409 — the environment was changed while this was being written; try again.

POST /v1/desks/{id}/intercepts

Makes a workspace of the desk take over a service of its environment, under every rule of POST /v1/environments/{id}/intercepts. This desk is the holder and the only one that releases it; leaving or deleting the desk releases it too.

FieldTypeRequiredRule
servicestringyesA service of the desk's environment that declares at least one port
workspacestringyesA workspace of this desk (id, name, or team/name), running
portsobject[]no{ "service": int, "workspace": int }, as on the environment route
json
{ "service": "api", "workspace": "api", "ports": [{ "service": 8080, "workspace": 3000 }] }

Response: 202, the desk document; intercepts now holds the wish.

Errors:

  • 404 — the desk or workspace is not found or not yours; this environment has no service named "api".
  • 409 — this desk has no environment; connect one with PUT /v1/desks/{id}/environment; api is not in this desk; create it in the desk; another desk holds the service (desk {name} ({owner}) already intercepts this service; it must be released first); that workspace is stopped; start it before it can answer for a service; the environment was changed while this was being written; try again.
  • 422 — the same port and service refusals as the environment route. They are a service with no ports, an undeclared port, port 0, and a port mapped twice. A port that lands on the tool server port 7788 is also refused.

DELETE /v1/desks/{id}/intercepts/{service}

Releases an intercept this desk holds. One that is not there succeeds. One held by another desk, or made on the environment directly, is refused. Takes no body.

Response: 204 with an empty body.

Errors:

  • 404 — the desk is not found or not yours.
  • 409 — this desk has no environment; connect one with PUT /v1/desks/{id}/environment; this desk does not hold api; the environment was changed while this was being written; try again.

GET /v1/desks/{id}/live/state

The document the desk live page draws. Only the desk's owner gets it. A team member, a superadmin and an API token get no answer. Reading it never wakes or holds the desk. A desk you own always answers 200.

Response: desk holds the id, name, team, state (stopped, idle or running) and idleSince. members lists the desk's workspaces by name, then its capsules, newest first. It holds at most 20 capsules: the live ones and those that ended in the last 24 hours. A workspace has phase (running, starting, stopped, between-pods or error), task (the running task of its main tree, or null) and interceptedServices. A capsule has phase (running or ended), command and exit (code, endedAt and removeAt, the time its record is deleted), which is null while it runs.

environment is null when the desk has none. Otherwise it holds id, name, phase, services (ready, failing, message, ports, image, interceptedBy, proxy, restartRequired), intercepts and volume. Only a running workspace's task is read from its tool server, with a 3 s limit. A part that cannot be read is null, and partErrors carries one sentence for it, keyed by the workspace or environment id.

Errors:

  • 404 — the desk is not found or not yours.

GET /v1/desks/{id}/live/capsules/{run}/logs

The output of one of the desk's capsules, as newline-delimited JSON with one {"line"} per line. A live capsule answers the last 2000 lines of its log. An ended one answers the output stored when it ended. Secret shapes are masked, and a line over 8 KiB is replaced by a note naming its size. {run} is the capsule id.

Errors:

  • 404 — the desk is not found or not yours, or the capsule does not belong to this desk.
  • 409 — capsule {id} has no output yet ({phase}).