Skip to content
DocsAll docs
Console

Workspaces API

Lists the API routes that create, change, run, copy, and reach a workspace.

Last updated

On this page

The workspace document

Every route that returns a workspace returns this object. A field marked "omitted" is absent when it has no value.

json
{  "id": "ws-0a1b2c3d4e5f6a7b",  "owner": "karthik",  "team": "",  "name": "backend",  "region": "centralindia",  "state": "ready",  "image": "ghcr.io/kloudlite/kloudlite-workspace",  "placement": "node-2",  "volume": "ws-0a1b2c3d4e5f6a7b",  "ssh": { "gateway": "wss://ws-centralindia.khost.dev/tunnel/ws-0a1b2c3d4e5f6a7b", "host_key": "ssh-ed25519 AAAA…" },  "packages": ["nodejs@22", "ripgrep"],  "base_packages": ["git", "openssh"],  "packages_status": { "ready": true, "reason": "Built", "message": "" },  "locks": [{ "entry": "nodejs@22", "version": "22.11.0", "rev": "0f1e2d3c…", "source": "nixhub" }],  "desk": "desk-9c8b7a6d5e4f3a2b",  "environment": "env-1f2e3d4c5b6a7988",  "ttl_secs": 86400,  "expires_at": "2026-10-19T09:30:00Z",  "trees": [{ "name": "fix-auth", "path": "/home/kl/.agents/fix-auth/workspace", "ready": true }]}
FieldTypeMeaning
idstringws- and 16 hex digits
ownerstringthe person's handle
teamstringthe team slug; "" for a personal workspace
namestringthe name you gave it
regionstringthe team's region
statestringcreating, ready, stopped, error, or deleted. Any other phase the node reports (starting, idle, unavailable) reads as creating
imagestringthe container image
placementstring or nullthe node that holds it; null until a node claims it
volumestring or nullthe workspace id once it has a snapshot, else null. The delete and ttl responses always carry null
sshobject, omittedgateway (the tunnel URL) and host_key (the key to pin), once the workspace has reported a key
packagesstring[]the list you asked for, not what is installed
base_packagesstring[], omittedthe platform's base set the profile was built with
packages_statuscondition, omittedwhether the profile for packages is built
locksobject[], omittedwhat each name@version entry resolves to: entry, version, rev, source (nixhub or mirror)
clone_ofstring, omittedthe source workspace id of a clone
taskstring, omittedwhat a delegated clone is for
deskstring, omittedthe desk that manages it. Set at creation, never changes
environmentstring, omittedthe environment its desk connects it to, as its node last converged it
degradedcondition, omittedpresent with reason StorageUnavailable while its volume is read-only and its host remounts it
decommissioningcondition, omittedpresent with reason NodeLeaving while its host is being retired; the workspace starts again on another host
restart_requiredcondition, omittedpresent with reason EnvChanged while the running workspace does not have the current env vars. The message names the variables, never the values. See POST /v1/workspaces/{id}/restart
restart_on_env_changeboolean, omittedtrue when an env change restarts the workspace
placedcondition, omittedpresent only when no node in the region has room for it (NoCapacity)
idle_sincestring, omittedRFC 3339; set while it is idle-stopped and not woken since
ttl_secsinteger, omittedunused seconds before it is deleted
expires_atstring, omittedRFC 3339; last active (else created) plus ttl_secs
treesobject[], omittedthe extra working directories that the platform has cut: name, path, ready, and reason while not ready

A condition is { "ready": bool, "reason": string, "message": string }.

Managed workspaces

A workspace made through POST /v1/desks/{id}/workspaces is managed by that desk. Its desk field names the desk and never changes. Only the desk itself (its tool token) can start, stop, delete, or set the ttl of it. Every other caller (your CLI login, a personal API token, another desk) gets 409 with {name} is managed by desk {desk}; use the desk from POST /v1/workspaces/{id}/stop, DELETE /v1/workspaces/{id}, and PUT /v1/workspaces/{id}/ttl. Stop the whole desk with POST /v1/desks/{id}/stop and start it with POST /v1/desks/{id}/start; delete a workspace with DELETE /v1/desks/{id}/workspaces/{workspace}.

Reading, ssh, tools, packages, snapshots, and clone stay open to the owner. A clone of a managed workspace is standalone. PATCH /v1/workspaces/{id} with desk is 400.

POST /v1/workspaces

Creates a workspace and returns it in creating. The platform places it and starts it; poll GET /v1/workspaces/{id} until state is ready.

FieldTypeRequiredRule
namestringyes1 to 40 lowercase letters, digits, or -; starts with a letter, ends with a letter or digit, no --. Not ws, env, api, admin, system, kd, bench, or bld-…, and not shaped like an id (ws- and 16 hex digits). Unique among your workspaces in that team
imagestringnoDefault is the platform workspace image
teamstringnoA team you belong to. Absent, empty, or your own handle means a personal workspace. Case is ignored
packagesstring[]noNix package names, each optionally pinned name@version (latest, N, N.N, or N.N.N). Every pin is resolved before anything is written
ttl_secsintegerno600 to 2592000. Absent means never
envobjectnoVariables for the container, name to value. At most 64; names are [A-Za-z_][A-Za-z0-9_]*; not HOME and not KD_…; a value is at most 32 KiB. A value of exactly ${{ secrets.NAME }} is a secret of the workspace's owner (the team for a team workspace)
devicestringnoA device of yours (its name or id) to run the workspace on instead of the cluster. See Device workspaces. The device must be online (409, device {name} is offline). The workspace takes no team, packages, or ttl_secs (400, 422), and the name must be new among your workspaces (409, a workspace named "{name}" already exists here)
accessstring[]noDevice workspaces only (422 otherwise). Names of extra sandbox access, each 1 to 32 of a-z, 0-9, -, at most 8. The device resolves each name from its built-in definitions and its own config; see Device workspaces. A name the device does not define keeps the workspace from starting (AccessUndefined)
json
{ "name": "backend", "team": "acme", "packages": ["nodejs@22", "ripgrep"] }

Response: 202, the workspace document in creating, with volume null and placement null.

Errors:

  • 400 — desk in the body: create a desk's workspace with POST /v1/desks/{id}/workspaces; a workspace made here is standalone.
  • 403 — your access to the team is paused.
  • 404 — no such team: you are not a member, or it does not exist (plain text; JSON from the region lookup).
  • 409 — a workspace of that name exists in that team (a workspace named "api" already exists here, plain text). Over quota (workspaces, diskGb, cpu, or memoryGb). The team has no region yet (JSON).
  • 422 — the name breaks the rule (JSON, the rule as the message). The name is bench (JSON, bench is a reserved name). ttl_secs is out of range (ttl_secs must be 600..=2592000 seconds). A package entry is malformed, unknown, or a pin that cache.nixos.org does not hold (JSON, names the nearest versions). An env entry breaks the rules above (JSON, env: …). A secret reference that does not exist (`DB_URL` is not a secret of acme, plain text).
  • 503 — the API cannot reach a package index to resolve a pin (JSON); or the API cannot bind the team's region.

GET /v1/workspaces

Your workspaces, as an array of documents. ?team={slug} lists your workspaces in that team; absent, or your own handle, lists the personal ones. Capsules are never listed.

Response: 200.

json
[  { "id": "ws-0a1b2c3d4e5f6a7b", "owner": "karthik", "team": "", "name": "backend", "region": "centralindia", "state": "stopped", "image": "ghcr.io/kloudlite/kloudlite-workspace", "placement": "node-2", "volume": "ws-0a1b2c3d4e5f6a7b", "packages": ["ripgrep"] }]

Errors:

  • 403 — ?team names a team where your access is paused.
  • 404 — no such team.

GET /v1/workspaces/{id}

One workspace. {id} is the id or a name; qualify a name with ?owner={team} (see the overview). A capsule id also answers here.

Response: 200, the workspace document.

Errors:

  • 404 — not found: no such workspace, or not yours to see.
  • 409 — the name matches more than one workspace; JSON with matches (see the overview).

PATCH /v1/workspaces/{id}

Replaces the package list. Entries that did not change keep their locks; new pins are resolved first, and nothing is written if any fails.

FieldTypeRequiredRule
packagesstring[]yesThe whole new list, in the form described under create. [] clears it
json
{ "packages": ["nodejs@22", "ripgrep", "jq"] }

Response: 200, the workspace document with the new packages and locks.

Errors:

  • 404 — not found, or not yours.
  • 422 — an entry is malformed, unknown, or an exact pin that is not cached (JSON).
  • 503 — a pin needs an index and the API cannot reach one (JSON).

POST /v1/workspaces/{id}/packages/update

Re-resolves every pinned entry against the index, bypassing the 24-hour cache, and moves the locks to the newest match. The package list itself does not change. Takes no body.

Response: 200, the workspace document. If every index is down the locks it had are kept and the answer is still 200.

Errors:

  • 404 — not found, or not yours.
  • 422 — a pinned entry no longer resolves (JSON).

POST /v1/workspaces/{id}/start

Runs a stopped workspace, or wakes an idle-stopped one and the desk it belongs to. Takes no body.

Response: 202 with an empty body.

Errors:

  • 404 — not found, or not yours.
  • 409 — your owner is over its disk limit (a start resumes filling).
  • 409 — the workspace is managed by a desk and the caller is not that desk: workspace {name} belongs to desk {desk}; start the desk: kode desks start --desk {desk}.

POST /v1/workspaces/{id}/stop

Stops a workspace. Its host syncs the disk and releases it. Takes no body.

Response: 202, the workspace document with state stopped.

Errors:

  • 404 — not found, or not yours.

POST /v1/workspaces/{id}/restart

Restarts the workspace so that it gets the current env vars. Takes no body. A changed env var or a rotated secret does not restart a workspace. It sets restart_required on the document. Use this route to apply the change, or set restartOnEnvChange with PUT /v1/workspaces/{id}/env.

When only secret values changed, the workspace container restarts in its place and keeps its address and its mounted home. Any other change, such as a literal value, a new reference or a new image, restarts the whole workspace with a new address.

A workspace on a device restarts when the device next lists its workspaces, within seconds while it is online.

Response: 202, the workspace document.

Errors:

  • 404 — not found, not yours, or a capsule.
  • 403 — your access to {team} is paused.
  • 409 — workspace is not running; its next start reads the current env.

DELETE /v1/workspaces/{id}

Deletes the working copy and its home. A volume that still has snapshots survives, detached, and can be restored from. Takes no body.

Response: 202, the document with state deleted and volume null.

json
{  "id": "ws-0a1b2c3d4e5f6a7b",  "owner": "karthik",  "team": "",  "name": "backend",  "region": "centralindia",  "state": "deleted",  "image": "ghcr.io/kloudlite/kloudlite-workspace",  "placement": "node-2",  "volume": null,  "packages": ["ripgrep"]}

Errors:

  • 404 — not found, or not yours.

POST /v1/workspaces/{id}/clone

On a device workspace the body is {"name", "snapshot"}, and snapshot is required. The new workspace is on the same device, and its folder is a clone of that snapshot of the source (see Devices API). Without it the answer is 422. A snapshot of another workspace, another owner or another device is 404.

Copies a workspace to a new one in the same team and region, current as of the call. The copy joins no desk unless the body names one with desk. It has no extra working directories and keeps the source's packages and locks.

FieldTypeRequiredRule
namestringyesThe same rule as create; unique in the source's team
taskstringnoWhat the copy is for; at most 200 characters, trimmed; empty means none
deskstringnoA desk of yours, by name, team/name, or id. The copy is born a member of it. An API token is refused with 422
json
{ "name": "api-fix", "task": "fix the login redirect" }

Response: 202, the workspace document of the new workspace plus based_on: snapshot (the cut that it grafted onto), at (RFC 3339, omitted for the fresh cut), age_seconds, and interrupted. Each clone makes a new cut of the source, so age_seconds is always 0 and interrupted is always false.

Errors:

  • 404 — the source is not found or not yours; you cannot spend the team's quota.
  • 409 — the name is taken in that team. The source has no volume yet (not ready yet: no volume for this workspace). A snapshot is already being cut for it. The storage pool is full (the storage pool is full; retry later or ask an operator). Over quota.
  • 422 — the name breaks the rule (JSON); task is longer than 200 characters (JSON); a package entry of the source no longer resolves.

POST /v1/workspaces/restore

A snapshot_id that starts with dsnap- is a device snapshot. The new workspace is made on the snapshot's device and cloned from it, as POST /v1/workspaces/{id}/clone does, with no source workspace needed. name and desk are the only other fields read.

Creates a new workspace from a snapshot, re-attaching to its volume even when the original workspace is gone. It works for a snapshot of a workspace only. Anything the body leaves out is what the snapshot froze.

FieldTypeRequiredRule
namestringyesThe same rule as create; unique in the new workspace's team
snapshot_idstringyesA reference: a volume name, name@N, or a snapshot id. The snapshot must be ready
teamstringfor a team volumeThe team whose volumes the reference is read from. The new workspace joins this team. Absent means your own volumes
deskstringnoA desk of yours, by name, team/name, or id. The new workspace is born a member of it. An API token is refused with 422
imagestringnoOverrides the frozen image
packagesstring[]noOverrides the frozen list; kept locks are reused, new pins are resolved

A volume of one owner never restores into another owner, by name or by id. To restore a team volume, send team. Without it, the call reads your own volumes, and the name of a team volume answers 404. The new workspace is in the team that team names, else personal.

json
{ "name": "api-restored", "snapshot_id": "ws-0a1b2c3d4e5f6a7b-8a7b6c91" }

Response: 202, the workspace document in creating.

Errors:

  • 400 — this snapshot was cut from an environment; use POST /v1/environments/{id}/clone with snapshot_id.
  • 404 — the snapshot does not exist, is not ready, or is not on a volume of the owner; volume {name} does not exist; volume {name} has no version {n}; volume {name} has no ready version; you cannot spend the team's quota.
  • 409 — the name is taken; over quota; the team has no region.
  • 422 — the name breaks the rule (JSON); a package entry does not resolve (JSON); a reference has a bad version such as name@0.
  • 503 — the API cannot reach a package index to resolve a pin (JSON).

POST /v1/workspaces/{id}/snapshots

On a device workspace this asks the device to take a snapshot and answers 202 {"request", "snapshot"} at once; see Devices API. GET on the same path lists the device workspace's snapshots.

Pushes a snapshot of the workspace. A snapshot is kept until it expires or you delete it, and keeps the volume alive once the workspace is gone. The first push of a volume must name it. See Names and versions.

FieldTypeRequiredRule
messagestringnoA note shown in the lineage
ttl_secsintegerno3600 to 31536000. Absent means the region's default (30 days unless an admin changed it)
neverbooleannotrue keeps it with no expiry. Exclusive with ttl_secs
volumestringon the first pushThe volume name: 1 to 40 characters of a-z, 0-9, and -; starts with a letter, ends with a letter or digit. After the first push it is optional, and when given it must equal the name
json
{ "message": "before the migration", "ttl_secs": 604800, "volume": "go-base" }

Response: 202. The snapshot is working until the platform has cut it; read it back from the lineage of the volume.

json
{ "id": "ws-0a1b2c3d4e5f6a7b-8a7b6c91", "phase": "working", "volume": "ws-0a1b2c3d4e5f6a7b", "expires_at": "2026-10-25T09:30:00+00:00", "name": "go-base", "version": 1 }

expires_at is null when never was set. volume is the volume id. name and version are the volume name and the number of this push.

Errors:

  • 400 — invalid snapshot body; or ttl_secs and never are exclusive.
  • 404 — not found, or not yours; the volume no longer exists.
  • 409 — not ready yet: no volume for this workspace; a snapshot is already being cut for this workspace; over the snapshots or disk limit.
  • 409 — also volume {name} already exists; delete it first: another volume of the owner has the name. this volume is named {a}, not {b}: volume differs from the name of the volume. the version counter of {name} is busy; retry.
  • 422 — ttl_secs must be between 3600 and 31536000; volume: this volume has no name yet; name it on its first push; volume {name}: … when the name breaks the rule.

PUT /v1/workspaces/{id}/ttl

Sets or clears how long the workspace can stay unused before it is deleted.

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

Response: 200, the workspace document with ttl_secs and expires_at; volume is null in this response.

Errors:

  • 404 — not found, or not yours.
  • 422 — ttl_secs must be 600..=2592000 seconds; a capsule takes no ttl.

PUT /v1/workspaces/{id}/access

Replaces the named extra sandbox access of a device workspace. The running workspace keeps the access it started with; a restart applies the new list.

FieldTypeRequiredRule
accessstring[]yesAs in POST /v1/workspaces. An empty list removes all
json
{ "access": ["xcode", "user-temp"] }

Response: 200, {"access": [...], "message": "restart {name} to apply"}.

Errors:

  • 404 — not found, or not yours.
  • 422 — access is for device workspaces only: …; a name that breaks the rule; more than 8 names.

GET /v1/workspaces/{id}/tools

The address of the workspace's tool server. Only the workspace's owner gets it. ?team={slug} asserts the workspace is in that team, and is refused when it is not.

Response: 200. address is the address and port 7788 of the workspace, reachable from your desks and the ssh tunnel only. token is the workspace's own credential for that server; it is left out, not null, until the platform has written it.

json
{ "address": "10.42.1.17:7788", "token": "eyJhbGciOi…" }

Errors:

  • 404 — not found, or not yours (a team member who is not the owner gets this too).
  • 409 (JSON) — the workspace is not ready (workspace api is stopped; start it to run tools); it is in a different team than ?team; or it is between restarts (try again).

POST /v1/workspaces/{id}/file-link

Names one file of the workspace for the person and for an MCP client. Only the workspace's owner gets an answer, with a login or from a desk's agent (the download tool). An API token gets no answer. Works for a cloud workspace and for a device workspace whose machine is online; the path stays inside the workspace's folder. Nothing in the answer is a credential.

Body: {"path": "out/report.pdf", "tree": "fix"}. path is relative to the workspace; tree is optional.

Response: 200.

json
{ "uri": "kode://workspace/ws-…/file/out/report.pdf?tree=fix", "url": "https://app.kloudlite.io/acme/workspaces/ws-…/files?path=out/report.pdf&tree=fix", "name": "report.pdf", "size": 48213, "mime": "application/pdf" }
  • url opens the file in the console. The person must be signed in as the owner; the console sends the file with their session.
  • uri is the MCP resource. An MCP client reads it with resources/read on its own connection.

Errors:

  • 400 — path must be relative and stay inside the workspace; path is not a regular file; tree is not a tree name.
  • 403 — a path that resolves outside the workspace, symlinks followed.
  • 404 — the workspace is not found or not yours, or the file is not found.
  • 409 — the workspace is not ready, or is between restarts.
  • 413 — the file is over 200 MB (200 000 000 bytes).

GET /v1/workspaces/{id}/files

The bytes of one file, ?path= and optional ?tree=, for the owner's login only (the console relays it from url above). A desk token and an API token get no answer. Images and PDFs are inline with their own type, text is inline as text/plain; charset=utf-8 (an HTML file shows as text), and anything else is an attachment. Every answer has Content-Length, Cache-Control: no-store, X-Content-Type-Options: nosniff and a Content-Security-Policy that starts with sandbox.

Errors:

  • 400 — the path leaves the workspace, or is not a regular file.
  • 401 — no login.
  • 403 — a path that resolves outside the workspace, symlinks followed; or the owner's access to the team is paused.
  • 404 — the workspace is not found or not yours, or the file is not found.
  • 409 — the workspace is not ready.
  • 413 — the file is over 200 MB.

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

The document the live page draws first. Only the workspace's owner gets it. A team member, a superadmin and an API token get no answer. Every phase answers 200.

Response: workspace holds the id, name, phase (running, starting, stopped, between-pods or error), image, uptime and package status. live is true when the tool server answered; otherwise why says what to do. trees lists the working directories.

git, tasks, journal and procs come from the tool server. volume comes from the platform's records and fills while the workspace is stopped. A part that failed is null, and partErrors carries one sentence for it. cursor is the highest journal id, the after of a stream. ?tree={name} reads another working directory.

Errors:

  • 404 — not found, or not yours.
  • 400 — tree is not a tree name.

GET /v1/workspaces/{id}/live/stream

A newline-delimited JSON stream of what happens in the workspace: hello, edit, task, journal, proc, reset, ping and a last closed frame that says why the stream ended. ?after={cursor} resumes after a journal id. The platform keeps the tool server's credential and never sends it to you. A workspace has at most 3 open streams, and an API replica at most 20.

Errors:

  • 404 — not found, or not yours.
  • 409 — the workspace is not ready, is between restarts, or runs an image without the live routes (update the workspace image).
  • 429 — too many viewers; the sentence names which limit.
  • 502 — the tool server did not answer.

GET /v1/workspaces/{id}/live/tree

One directory of the workspace, as { "path", "truncated", "entries" }: directories first, each entry with name, kind, size, mtime, ignored and git (its change letter, empty when clean). ?path= is relative to the workspace directory and defaults to its root, ?depth= is 1 to 3 levels (default 1) and ?tree= picks the working directory.

Errors:

  • 400 — the path is absolute, holds .. or leaves the workspace, the depth is out of range, or the working directory is unknown.
  • 404 — not found, or not yours; the directory does not exist.
  • 409 — the workspace is not ready.
  • 502 — the tool server did not answer.

GET /v1/workspaces/{id}/live/file

One file of the workspace, as { "path", "size", "text" }. ?path= is relative to the workspace directory and ?tree= picks the working directory. A binary file, or one over 1 MiB, answers { "path", "placeholder" } and a sentence in place of its bytes.

Errors:

  • 400 — the path is empty, absolute, holds .., or leaves the workspace.
  • 404 — not found, or not yours; the file does not exist.
  • 409 — the workspace is not ready.

GET /v1/workspaces/{id}/live/diff

The working-tree diff of one file against HEAD, as { "diff" } in unified format. A binary file, or a diff over the size limit, answers { "placeholder" } with the reason. ?path= and ?tree= as for live/file.

Errors: as for live/file.

GET /v1/workspaces/{id}/live/procs/{pid}/log

The output of one background process as newline-delimited JSON, one { "line" } per line. The platform masks tokens and secret assignments, and cuts a line over 8 KiB to a sentence that names its size. The stream ends when the process exits.

Errors:

  • 400 — pid is not a process id.
  • 404 — not found, or not yours.
  • 409 — the workspace is not ready.

POST /v1/workspaces/{id}/trees

Asks for an extra working directory: a writable copy of the live one, for one subagent. Poll trees on the workspace until the entry is ready. It costs no quota, and the region limits how many a workspace holds.

FieldTypeRequiredRule
namestringyes1 to 32 characters of a-z, 0-9, and -. Unique in the workspace
json
{ "name": "fix-auth" }

Response: 202. The working directory does not exist yet.

json
{ "name": "fix-auth", "path": "/home/kl/.agents/fix-auth/workspace" }

Errors:

  • 404 — not found, or not yours.
  • 409 — the workspace is not running; tree fix-auth exists; trees: 4 of 4 in use; the workspace changed while this was written (try again).
  • 422 — a tree name is 1 to 32 characters of a-z, 0-9 and -.

DELETE /v1/workspaces/{id}/trees/{name}

Gives an extra working directory back. It leaves the list at once. The platform removes the directory later.

Response: 202 with an empty body.

Errors:

  • 404 — the workspace or the working directory is not found (a name that was never asked for is not an idempotent success).
  • 409 — the workspace changed while this was written (try again).

POST /v1/workspaces/{id}/ssh-session

A one-shot ticket to open an ssh tunnel to a ready workspace, which kode presents to the gateway. Takes no body. Accepts a session or CLI login token only.

Response: 201. {id} can be a name; the answer carries the id it resolved to. token is short-lived, host_key is the key to pin.

json
{  "id": "ws-0a1b2c3d4e5f6a7b",  "token": "eyJhbGciOi…",  "gateway": "wss://ws-centralindia.khost.dev/tunnel/ws-0a1b2c3d4e5f6a7b",  "expires_at": "2026-10-18T09:35:00Z",  "host_key": "ssh-ed25519 AAAA…"}

Errors:

  • 404 — not found, or not yours.
  • 409 (JSON) — workspace is stopped (or whichever phase it is in; only ready gets a ticket).
  • 500 — the API cannot sign the ticket.
  • 503 — the workspace has not reported its host key yet; retry.

PUT /v1/workspaces/{id}/env

Replaces the workspace's whole env: variables you leave out are removed. The new environment reaches the workspace the next time it starts. A running workspace keeps what it has and shows restart_required with the names of the changed variables. A value of exactly ${{ secrets.NAME }} is read from the owner's secrets at start. Only the owner can call it, and a desk tool token is refused.

FieldTypeRequiredRule
envobjectyesName to value, the same rules as env on create. {} clears it
restartOnEnvChangebooleannotrue restarts the workspace when its env vars change, false only marks it. Default false; absent keeps the current value
json
{ "env": { "LOG": "debug", "DB_URL": "${{ secrets.DB_URL }}" } }

Response: 200, the workspace document.

Errors:

  • 404 — not found, or not yours.
  • 422 — a capsule's env is set at create (plain text). An env entry breaks the rules (JSON: env: at most 64 variables, env: "1X" is not a variable name, env: HOME is set by the platform, env: LOG is longer than 32 KiB). `DB_URL` is not a secret of acme (plain text); the scope is the team of the workspace, or its owner for a personal workspace.

GET /v1/workspaces/registries

Lists the container registries that this workspace's builds can push to. kd calls it with the workspace token. The list holds your own registry connectors first and then the connectors of the team of the workspace. It holds no credential.

Response: 200, an array. An owner without a connected registry gets [].

FieldTypeMeaning
connectorstringThe connector name, for example acme-dockerhub-1
hoststringThe registry host, for example docker.io
namespacestringThe path that images are pushed under. Empty when the connector names none
personalbooleantrue for a connector of yours, false for one of the team

Errors: 403 — your access to the team is paused or removed.

POST /v1/workspaces/registry-credential

Returns the login for one registry host, created for this request. docker-credential-kd calls it with the workspace token each time docker asks for a login. Only a connector that the workspace can use is resolved: yours, then the team's. The connector of another member is never resolved.

FieldTypeRequiredRule
hoststringyesA host from GET /v1/workspaces/registries. docker.io is Docker Hub
json
{ "host": "docker.io" }

Response: 200.

FieldTypeMeaning
usernamestringThe user name to sign in with
secretstringThe secret to sign in with. Do not store it
expiresAtinteger or nullWhen the login stops working, in milliseconds since the epoch. null for a pasted credential

Errors:

  • 404 — no registry is configured when no registry is connected. no connected registry has that host when none of the connectors you can use has that host.
  • 409 — the connector has no usable credential, for example connector acme-dockerhub-1 is gone.