# WorkspaceService

**Spinbox is experimental: APIs change and data may be lost.** What is built and what proves it is on [Status](https://spin-stack.github.io/website/reference/status.md); what Spinbox does not do is on [Limitations](https://spin-stack.github.io/website/overview/limitations.md).

> spin.v1.WorkspaceService: each procedure, its request and its response.

<!-- Generated by tools/refgen from spin:internal/controlplane/apidocs/spin.v1.openapi.json. Edit the source, not this file. -->

## AddWorkspaceGuest

`POST /spin.v1.WorkspaceService/AddWorkspaceGuest`

Invite a person to log in to a workspace as its guest, by their email: the owner's to ask, and the next access the owner signs names them. They log in once it does.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `email` | string |  |

**Response**

No fields: `{}`.

## AttachVSCodeServer

`POST /spin.v1.WorkspaceService/AttachVSCodeServer`

Give a running workspace the VS Code server of a client's commit, while it runs: published first when this installation has not got it and fetches servers on its own (InstallationSettings.vscode_fetch), refused as FAILED_PRECONDITION when it does not. Asked again until it answers attached.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `commit` | string | commit is 40 hex digits. |
| `publish` | boolean | publish tries a publish of commit again when the last one failed; without it, that failure is the answer. A client sets it on its first ask, and not on those that follow. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `attached` | boolean | attached: the workspace has the server now. Otherwise it is being published, and step says where that is. |
| `step` | string |  |
| `detached` | list of string | detached are the servers the workspace gave up to make room, asked for longest ago. |

## CheckPortAccess

`POST /spin.v1.WorkspaceService/CheckPortAccess`

Whether one request may reach one published port. The gate in front of them.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceName` | string | From the subdomain |
| `port` | integer (int32) |  |
| `sessionToken` | string | The session token as the proxy received it, empty for a request with no session at all. Only a PUBLIC port is reachable that way. |
| `httpsBackend` | boolean | As in OpenWorkspaceProxySessionRequest |

**Response**

| Field | Type | What it is |
|---|---|---|
| `status` | [WorkspaceProxyStatus](https://spin-stack.github.io/website/reference/api/types.md#workspaceproxystatus) |  |
| `allowed` | boolean |  |
| `access` | [PortAccess](https://spin-stack.github.io/website/reference/api/types.md#portaccess) | What the port's level is, so the proxy can tell a refusal that a login would fix from one it would not: an anonymous request to a PRIVATE port is a redirect, and a signed-in request to the same port is a 403 no amount of signing in changes. |
| `workspaceId` | string | The workspace the name was decided about, when allowed. The proxy pools connections by it and opens sessions for it: a name is freed by a rename and taken by someone else's workspace, and a connection pooled by name would carry the next request into the old one. |

## CheckPortPublished

`POST /spin.v1.WorkspaceService/CheckPortPublished`

Whether a published port's name is one this installation serves, whoever may reach it: the proxy's on-demand TLS question, asked before any browser has a session to send.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceName` | string | From the subdomain |
| `port` | integer (int32) |  |
| `httpsBackend` | boolean | As in CheckPortAccessRequest: 3000s- is not 3000- |

**Response**

| Field | Type | What it is |
|---|---|---|
| `published` | boolean | The port is published on a workspace that is not deleted, at any level and in any state. |

## CloneWorkspace

`POST /spin.v1.WorkspaceService/CloneWorkspace`

Clone a running workspace: a snapshot of its disk, published, and a workspace created from it with the source's team, project and ref, size, network policies and environment, owned by the caller. Disk only: a clone has an identity of its own, so no memory goes with it. The snapshot stays, as the clone's floor. Answers once the clone exists, which takes as long as publishing the source's disk.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `name` | string | The clone's name; generated when empty. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |
| `snapshot` | [Snapshot](https://spin-stack.github.io/website/reference/api/types.md#snapshot) | The source's snapshot the clone was made from, which stays as the clone's floor. |

## CreateSnapshot

`POST /spin.v1.WorkspaceService/CreateSnapshot`

Create a snapshot of the current workspace state

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `description` | string | Optional description |
| `withMemory` | boolean | with_memory keeps the machine's memory too, taken at the instant the disk is sealed; the guest is paused for tens of milliseconds, and the call returns once the memory is written. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `snapshot` | [Snapshot](https://spin-stack.github.io/website/reference/api/types.md#snapshot) |  |

## CreateWorkspace

`POST /spin.v1.WorkspaceService/CreateWorkspace`

Create a new workspace

**Request**

| Field | Type | What it is |
|---|---|---|
| `name` | string | Optional, generated if empty |
| `teamId` | string | Optional: team ID (personal workspace if empty) |
| `image` | string | A published image (ListImages); required unless from_snapshot_id is set |
| `preset` | string | The preset it is sized by, by name (AdminService.ListResourcePresets): its cores, memory, disk and how it grows. Empty is the installation's default preset, or with none marked 1 core, 512 MB and 10 GB free. A disabled preset is refused. |
| `env` | map of string to string | Environment variables |
| `fromSnapshotId` | string | Create workspace from a shared snapshot |
| `networkTags` | list of string | network_tags are the network policies this workspace runs under, by name - the same idea as a GCP instance's network tags. The host's ruleset names the tag and holds the set of machines carrying it, so this decides which rules apply without naming a rule. Empty runs the workspace under the installation's default, and under "offline" where it declares none: a create that names nothing reaches nothing, rather than everything. A tag the host has no policy for fails the start rather than being ignored: a workspace asking for a policy the node does not have is a question with two answers, and quietly choosing "run it without" is how one ends up less contained than whoever launched it believes. |
| `projectId` | string | A project of a team the caller is in: the workspace is the project's - its team, image, preset and tags, which the request then names none of - and its guest clones the project's repository at ref, the project's own when empty. |
| `ref` | string |  |
| `requestId` | string | Optional, at most 128 bytes: the client's key for this create, so a retry after an answer it never got is answered with the workspace the first attempt made. The same key with another request is refused (FAILED_PRECONDITION). |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## DeleteSnapshot

`POST /spin.v1.WorkspaceService/DeleteSnapshot`

Delete a snapshot

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `snapshotId` | string |  |

**Response**

No fields: `{}`.

## DeleteWorkspace

`POST /spin.v1.WorkspaceService/DeleteWorkspace`

Delete a workspace: into the trash, where the installation keeps one, or erased.

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string |  |
| `force` | boolean | Force delete regardless of state (skips state validation) |
| `permanently` | boolean | permanently erases the workspace now - its disk, snapshots and keys - rather than putting it in the trash; a workspace already in the trash is erased only this way. What is erased cannot be undeleted: the store expires its bytes when its retention ends. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `trashedUntil` | `Timestamp` | Until when it can be undeleted; unset when it was erased. |

## DiffSnapshots

`POST /spin.v1.WorkspaceService/DiffSnapshots`

Compare two of a workspace's snapshots file by file.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `fromSnapshotId` | string | Empty compares from what the workspace was created from. |
| `toSnapshotId` | string |  |
| `under` | string | under narrows the comparison to one path of the filesystem, from its root; empty is all of it. The walk starts there rather than filtering, so what is outside it costs nothing and does not fill the answer. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `changes` | list of [FileChange](https://spin-stack.github.io/website/reference/api/types.md#filechange) | Every change from one snapshot to the other, a directory before what is under it. |
| `truncated` | boolean | truncated says there are more changes than one answer carries. |

## ExposePort

`POST /spin.v1.WorkspaceService/ExposePort`

Port exposure

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `port` | integer (int32) |  |
| `httpsBackend` | boolean | See ExposedPort.https_backend |
| `description` | string |  |
| `access` | [PortAccess](https://spin-stack.github.io/website/reference/api/types.md#portaccess) | Left unset, this is PRIVATE. Exposing a port is not the same as publishing it. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `exposedPort` | [ExposedPort](https://spin-stack.github.io/website/reference/api/types.md#exposedport) |  |

## GetOwnerWord

`POST /spin.v1.WorkspaceService/GetOwnerWord`

A workspace's newest access - who logs in, consent to collect logs, the settings approved - with what the next must say: the people the owner invited and the settings the guest would be handed now, for the owner to sign. Its owner's and its guests' to read.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `word` | [OwnerWord](https://spin-stack.github.io/website/reference/api/types.md#ownerword) | word is the newest the owner signed; unset for a workspace whose owner has signed none. |
| `owner` | [AccessPerson](https://spin-stack.github.io/website/reference/api/types.md#accessperson) | owner is the workspace's owner, with their keyring as it is now: who signs the next. Empty keyring for an owner who has signed none. |
| `dotfiles` | [DotfilesConfig](https://spin-stack.github.io/website/reference/api/types.md#dotfilesconfig) | settings are the startup configuration the guest would be handed at its next registration: the owner's settings after the team's policy. A word approves them by their digest, which the client computes from these, as the guest does from what it is handed. |
| `defaultShell` | string |  |
| `env` | map of string to string |  |
| `editorSettings` | map of string to string |  |
| `taskfile` | string |  |
| `egressRoot` | string | egress_root is the installation's egress root, PEM: what the guest is handed at registration, and trusts only when a word names its digest. A client names it only once it has held it to the root it pinned for the installation, never on this answer's say alone. |
| `identityAudiences` | list of string | identity_audiences is who the installation gives identities for, empty for anybody: what a client offers its owner to name in a word, which a host holds every token to as well. |
| `teamId` | string | The workspace as its first access says it: team, image, and the snapshot and workspace it was cloned from. The next access says the same; the first says these. |
| `image` | string |  |
| `parentSnapshotId` | string |  |
| `parentWorkspaceId` | string |  |
| `guests` | list of [AccessPerson](https://spin-stack.github.io/website/reference/api/types.md#accessperson) | guests are the people the owner invited, each with their keyring as it is now: who the next access names besides the owner. One with an empty keyring has signed none, and is named once they have. |
| `role` | string | role is the caller's: "owner", or "guest" - who reads it and signs nothing. |
| `repository` | [RepositoryConfig](https://spin-stack.github.io/website/reference/api/types.md#repositoryconfig) | repository is the one the workspace opened, part of the settings a word approves as the five above are: unset for a workspace that opened no project. |
| `prebuildClaim` | string (byte) | prebuild_claim is, for a workspace opened from a prebuild, the claim the host that built it signed (PrebuildClaim), whose SHA-256 repository.prebuild_claim_digest is: a client verifies it, and that it names the repository and the commit the settings do, before its word approves them. |
| `prebuildWriters` | map of string to string (byte) | prebuild_writers is the record of the claim's writer, by its id (writers/&lt;id>.json in the installation's store), which the client holds to its own anchors as it does a launch claim's. |

## GetWorkspace

`POST /spin.v1.WorkspaceService/GetWorkspace`

Get a specific workspace

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string | Workspace ID or name |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## GetWorkspaceConsole

`POST /spin.v1.WorkspaceService/GetWorkspaceConsole`

The tail of the workspace's serial console: the kernel, vminit, then the supervisor. It is where a machine that never came up says why, and it is read on demand from the host that holds it. An installation that collects consoles also keeps them, searchable for their retention (LogService.SearchWorkspaceLogs); one that does not keeps them on the host, since the console also carries what the tenant's own programs print.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `lines` | integer (int32) | How many lines from the end; zero asks for the default. The host bounds it either way. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `lines` | list of string | Newest last, as the console was written. |
| `truncated` | boolean | truncated says there are lines before the first one here, so what this begins with is not mistaken for the start of the boot. |
| `hostId` | string | Which host answered. A console belongs to the machine that wrote it, and a workspace that has moved has nothing to say about the run that failed on the host it left. |

## GetWorkspaceEgress

`POST /spin.v1.WorkspaceService/GetWorkspaceEgress`

What a running workspace has actually reached beyond the node.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `destinations` | list of [ObservedDestination](https://spin-stack.github.io/website/reference/api/types.md#observeddestination) | Most traffic first, so what an allowlist has to carry is at the top. |
| `lookups` | list of [ResolverLookup](https://spin-stack.github.io/website/reference/api/types.md#resolverlookup) | The workspace's recent lookups its host's resolver did not answer as asked, newest first: "it cannot resolve github.com" is an upstream failing, a name pointing into the node being removed, or a query type refused, and this says which. Answered lookups are not kept. |
| `refused` | list of [RefusedDestination](https://spin-stack.github.io/website/reference/api/types.md#refuseddestination) | What the host's drops refused the workspace in the last hour, most packets first: the node's own networks, or what none of its policies allows. The other half of destinations, and what a policy that is too narrow looks like from outside the machine. |
| `brokered` | list of [BrokeredRequest](https://spin-stack.github.io/website/reference/api/types.md#brokeredrequest) | The workspace's recent requests to names its policies broker with an injection, newest first: what the host's egress proxy set a header on, and what it refused and why. A brokered name no rule injects on is relayed byte for byte and leaves no request here. |

## GetWorkspaceHistory

`POST /spin.v1.WorkspaceService/GetWorkspaceHistory`

Get workspace history (snapshots/state changes timeline)

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string |  |
| `limit` | integer (int32) | Number of events (default: 50) |

**Response**

| Field | Type | What it is |
|---|---|---|
| `events` | list of [WorkspaceEvent](https://spin-stack.github.io/website/reference/api/types.md#workspaceevent) |  |

## GetWorkspaceIdentity

`POST /spin.v1.WorkspaceService/GetWorkspaceIdentity`

Who the workspace is outside spin: the identity its tokens and signatures carry, the issuer a relying party trusts it by, and what it was cloned from.

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `spiffeId` | string | The workspace's SPIFFE ID, spiffe://&lt;domain>/t/&lt;team>/w/&lt;workspace>: every subject in it is under it - the user's at /u, each agent's at /a/&lt;name> - so a relying party names the workspace by it as a prefix. |
| `userSubject` | string | The workspace user's subject, what its tokens and signatures carry. |
| `issuer` | string | The issuer its tokens name, https://id.&lt;domain>, where the key set and the allowed signers are. |
| `parentSnapshotId` | string | What the workspace was cloned from: the snapshot, and the workspace whose disk it was when that is still there and of the same team. Empty for a workspace made from an image. |
| `parentWorkspaceId` | string |  |
| `audiences` | list of string | Who the installation gives identities for; empty is anybody. |

## GetWorkspaceStatus

`POST /spin.v1.WorkspaceService/GetWorkspaceStatus`

What every party believes about one workspace, and where they disagree.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `state` | [WorkspaceState](https://spin-stack.github.io/website/reference/api/types.md#workspacestate) | What the control plane believes, which is what every other surface shows. |
| `failureReason` | string | Only when state = FAILED |
| `supervisor` | [SupervisorStatus](https://spin-stack.github.io/website/reference/api/types.md#supervisorstatus) |  |
| `host` | [WorkspaceHostStatus](https://spin-stack.github.io/website/reference/api/types.md#workspacehoststatus) |  |
| `volume` | [WorkspaceVolumeStatus](https://spin-stack.github.io/website/reference/api/types.md#workspacevolumestatus) |  |
| `disagreements` | list of [StatusDisagreement](https://spin-stack.github.io/website/reference/api/types.md#statusdisagreement) | Where the four disagree, in the order they should be read. Empty means they agree - which is the answer when a workspace is simply healthy, and is worth saying. |
| `runtime` | [WorkspaceRuntime](https://spin-stack.github.io/website/reference/api/types.md#workspaceruntime) | What the machine has and uses now, asked of its host. Absent - not zero - when there is no machine to ask about: not running, on no connected host, or a host that did not answer in time. Zero cores would read as a machine that has none. |
| `failure` | [WorkspaceFailure](https://spin-stack.github.io/website/reference/api/types.md#workspacefailure) | Which part failed, when state = FAILED; failure_reason is the detail. Workspace.failure. |
| `injections` | list of [InjectionStatus](https://spin-stack.github.io/website/reference/api/types.md#injectionstatus) | Each header a brokered rule of the workspace's policies sets, and whether what it is set from is there to set: a request to a brokered name is refused when it is not, and this is where that is seen from outside the machine. |

## GrantPortAccess

`POST /spin.v1.WorkspaceService/GrantPortAccess`

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `port` | integer (int32) |  |
| `userId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `exposedPort` | [ExposedPort](https://spin-stack.github.io/website/reference/api/types.md#exposedport) |  |

## GrantWorkspaceResources

`POST /spin.v1.WorkspaceService/GrantWorkspaceResources`

Give a workspace more cores and memory of its own, up to its preset's ceiling: its host reserves them and its machine is grown while it runs. The grant lasts until it is released or the workspace is deleted.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `cpuCores` | integer (int32) |  |
| `memoryMb` | integer (int32) |  |
| `lasts` | `Duration` | How long the grant lasts before it is given back on its own, from now: at least a minute. Unset keeps it until it is given back. Each grant says it anew: one that says nothing about time is kept, whatever the one before it said. |
| `move` | boolean | move says what to do when a running workspace's host has no room for the grant: move it to a host that has - suspended where it runs, its processes as they are, and resumed there from the checkpoint. The guest is paused from the checkpoint until it runs on the other host, which takes as long as storing its memory and publishing its disk. Without it such a grant is refused. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## KeepWorkspace

`POST /spin.v1.WorkspaceService/KeepWorkspace`

Keep a running workspace awake for a while: neither the idle suspension nor the running cap fires before kept_until, and the question asked before one (suspends_at) is withdrawn. An answer with an end, never permanent: at most a day at a time (MaxKeepMinutes).

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `lasts` | `Duration` | How long from now it is kept awake: a minute to a day. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## ListDeletedWorkspaces

`POST /spin.v1.WorkspaceService/ListDeletedWorkspaces`

The caller's workspaces in the trash, and until when each can be undeleted.

**Request**

No fields: `{}`.

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspaces` | list of [DeletedWorkspace](https://spin-stack.github.io/website/reference/api/types.md#deletedworkspace) |  |

## ListExposedPorts

`POST /spin.v1.WorkspaceService/ListExposedPorts`

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `page` | [PageRequest](https://spin-stack.github.io/website/reference/api/types.md#pagerequest) |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `ports` | list of [ExposedPort](https://spin-stack.github.io/website/reference/api/types.md#exposedport) |  |
| `page` | [PageResponse](https://spin-stack.github.io/website/reference/api/types.md#pageresponse) |  |

## ListImages

`POST /spin.v1.WorkspaceService/ListImages`

The images a workspace can actually be created from. A client that offers a name from anywhere else - a constant it ships, a container registry it knows of - offers a create that fails, because a workspace's disk is cloned from a published image and nothing else.

**Request**

No fields: `{}`.

**Response**

| Field | Type | What it is |
|---|---|---|
| `images` | list of [Image](https://spin-stack.github.io/website/reference/api/types.md#image) | One entry per name, newest publication of it, in the order a person reads a list. |

## ListNetworkPolicies

`POST /spin.v1.WorkspaceService/ListNetworkPolicies`

The network policies a workspace can be created with, as the hosts report them.

**Request**

No fields: `{}`.

**Response**

| Field | Type | What it is |
|---|---|---|
| `policies` | list of [NetworkPolicy](https://spin-stack.github.io/website/reference/api/types.md#networkpolicy) |  |
| `hostsEnforcing` | integer (int32) | Of the hosts connected, how many have installed what the installation declares. They are told on their next heartbeat, so the two differ for a moment after an apply - and go on differing when a host cannot install what was declared, which is the thing worth seeing. |
| `hostsConnected` | integer (int32) |  |

## ListSnapshots

`POST /spin.v1.WorkspaceService/ListSnapshots`

List snapshots for a workspace

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `page` | [PageRequest](https://spin-stack.github.io/website/reference/api/types.md#pagerequest) |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `snapshots` | list of [Snapshot](https://spin-stack.github.io/website/reference/api/types.md#snapshot) |  |
| `page` | [PageResponse](https://spin-stack.github.io/website/reference/api/types.md#pageresponse) |  |

## ListWorkspaceQuarantineDenials

`POST /spin.v1.WorkspaceService/ListWorkspaceQuarantineDenials`

What a workspace attempted past its policy: the refusals its supervisor reported, newest first - an administrator's reading of what was tried while it was held.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string | The workspace's id. |
| `limit` | integer (int32) | How many of the newest to answer; the newest first. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `denials` | list of [QuarantineDenial](https://spin-stack.github.io/website/reference/api/types.md#quarantinedenial) | Newest first. |

## ListWorkspaces

`POST /spin.v1.WorkspaceService/ListWorkspaces`

List workspaces for the authenticated user

**Request**

| Field | Type | What it is |
|---|---|---|
| `page` | [PageRequest](https://spin-stack.github.io/website/reference/api/types.md#pagerequest) |  |
| `stateFilter` | [WorkspaceState](https://spin-stack.github.io/website/reference/api/types.md#workspacestate) | Optional filter by state |
| `teamId` | string | Optional filter by team (empty = all user's workspaces) |
| `nameFilter` | string | Optional search by name prefix |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspaces` | list of [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |
| `page` | [PageResponse](https://spin-stack.github.io/website/reference/api/types.md#pageresponse) |  |

## MoveWorkspace

`POST /spin.v1.WorkspaceService/MoveWorkspace`

Move a running workspace to another host: suspended where it runs, its processes as they are, and resumed there from the checkpoint, holding what it holds. Placement chooses the host, as it does at a start. A workspace that is not running is refused: placement decides where it runs at its next start.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## OpenWorkspaceProxySession

`POST /spin.v1.WorkspaceService/OpenWorkspaceProxySession`

One connection into a published port, for the proxy, when it needs a new one.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceName` | string | From the subdomain, as CheckPortAccess takes it |
| `port` | integer (int32) | The published port inside the workspace |
| `sessionToken` | string | The session token of whoever the connection is being opened for, validated here. It is the difference between a control plane that can refuse and one that cannot. The proxy gates each request in front, but this is reachable on its own, and without a credential to check, anything that could reach it would open a session into any published port of any workspace. Empty is allowed and reaches only PUBLIC ports. |
| `httpsBackend` | boolean | The hostname's s. A port whose flag differs answers as one that is not exposed. |
| `workspaceId` | string | The workspace CheckPortAccess decided the request about. A name that now names another answers as a port that is not exposed: the session is for the workspace that was decided. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `status` | [WorkspaceProxyStatus](https://spin-stack.github.io/website/reference/api/types.md#workspaceproxystatus) |  |
| `relayUrl` | string | Where the two ends of this connection meet, and the token that names it there. Set only when status is ACCESSIBLE. The token is single-use: one connection, one token. |
| `sessionToken` | string |  |
| `httpsBackend` | boolean | The flag of the row the session was authorized on: whether the proxy speaks TLS. |

## QuarantineWorkspace

`POST /spin.v1.WorkspaceService/QuarantineWorkspace`

Hold a workspace in quarantine: an administrator's decision, held until one releases it. A stopped workspace is held at once; one whose machine runs is stopped first, forced, and held when its host says it stopped. Its disk is kept as it was, and meanwhile its own person cannot start, move or delete it.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string | The workspace's id. |
| `reason` | string | What the administrator saw that led to holding it: kept on the audit entry for this act, not on the row. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## RecoverWorkspace

`POST /spin.v1.WorkspaceService/RecoverWorkspace`

Re-home a workspace whose host is gone, at a cost its owner accepts.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `acceptDataLoss` | boolean | False asks what it would cost. True accepts that cost and does it. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `recovered` | boolean | False for a cost report, true when the workspace was re-homed. |
| `unpublishedSeconds` | string (int64) | What recovering costs: the work the dead host had not published, as of its last report, plus the time since - a host that has been silent for an hour has been writing for that hour too, and reporting only what it last said would understate it. |
| `unpublishedBytes` | string (int64) |  |
| `hostLastReportedAt` | `Timestamp` | When that report was made. The two numbers above are only as fresh as this. |
| `newHostId` | string | The host it now runs on. Set only when recovered. |

## ReleaseWorkspaceQuarantine

`POST /spin.v1.WorkspaceService/ReleaseWorkspaceQuarantine`

Release what was quarantined: stopped, with its disk as it was - or, where the stop a quarantine asked for ended FAILED, failed as it is, and no longer held.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string | The workspace's id. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## ReleaseWorkspaceResources

`POST /spin.v1.WorkspaceService/ReleaseWorkspaceResources`

Give a grant back: the workspace holds its floor again, and its machine is shrunk while it runs.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## RemoveWorkspaceGuest

`POST /spin.v1.WorkspaceService/RemoveWorkspaceGuest`

Take a guest out: refused at the control plane at once, and at the workspace once the owner signs an access without them.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `userId` | string |  |

**Response**

No fields: `{}`.

## RestoreWorkspace

`POST /spin.v1.WorkspaceService/RestoreWorkspace`

Put a stopped workspace's disk back to one of its snapshots, or to what it was created from.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `snapshotId` | string | One of this workspace's own published snapshots. Empty restores what the workspace was created from - its image's snapshot as it was at creation, not the one the image names now. |
| `withMemory` | boolean | with_memory resumes the snapshot's memory too: the workspace's processes go on from the instant the snapshot was taken, running or stopped before. Only for a snapshot taken with memory; a running workspace is resumed only on its own host. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## RevokePortAccess

`POST /spin.v1.WorkspaceService/RevokePortAccess`

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `port` | integer (int32) |  |
| `userId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `exposedPort` | [ExposedPort](https://spin-stack.github.io/website/reference/api/types.md#exposedport) |  |

## SetOwnerWord

`POST /spin.v1.WorkspaceService/SetOwnerWord`

Take an access the owner signed with a key of their keyring, and carry it to the guest: refused unless their newest keyring holds the key, of this workspace, at a counter past the one kept, saying what the first said of the workspace. The control plane writes no access; it only keeps and carries the owner's.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `word` | [OwnerWord](https://spin-stack.github.io/website/reference/api/types.md#ownerword) |  |

**Response**

No fields: `{}`.

## SetPortAccess

`POST /spin.v1.WorkspaceService/SetPortAccess`

Who may reach a published port, and who it is shared with.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `port` | integer (int32) |  |
| `access` | [PortAccess](https://spin-stack.github.io/website/reference/api/types.md#portaccess) |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `exposedPort` | [ExposedPort](https://spin-stack.github.io/website/reference/api/types.md#exposedport) |  |

## SetWorkspaceNetworkTags

`POST /spin.v1.WorkspaceService/SetWorkspaceNetworkTags`

Change which egress policies a workspace runs under, while it runs.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `networkTags` | list of string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |
| `droppedConnections` | integer (int32) | dropped_connections is how many of the machine's open connections were cut, because the change took a policy away and what a policy no longer allows must not survive in a socket that was opened before it. Zero when the change only added. |

## SetWorkspaceNetworkWatch

`POST /spin.v1.WorkspaceService/SetWorkspaceNetworkWatch`

Watch where a workspace connects: its host records each lookup, each destination it is let through to and each it is refused, with the time, into the workspace's logs.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `watch` | boolean |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## StartWorkspace

`POST /spin.v1.WorkspaceService/StartWorkspace`

Start a stopped workspace on its own disk, or resume a suspended one from the memory it kept.

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string |  |
| `fromDisk` | boolean | Boot a suspended workspace from its disk, dropping the memory it was suspended with: what is left when that memory was taken on a machine no host runs now (WORKSPACE_FAILURE_MACHINE_CHANGED). The guest boots as from a power cut. Refused for a workspace that is not suspended. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## StopWorkspace

`POST /spin.v1.WorkspaceService/StopWorkspace`

Stop a running workspace. Its disk is its volume, kept as it is; no snapshot is taken.

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string |  |
| `force` | boolean | Force kill instead of graceful |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## SuspendWorkspace

`POST /spin.v1.WorkspaceService/SuspendWorkspace`

Put a running workspace down with its processes where they are: its memory and its disk are checkpointed and the machine is stopped, and its next start resumes that instant - on any host carrying the same machine. The same thing a drained host does to what it runs. The guest is paused from the checkpoint until it is resumed: it does nothing in between, and nothing it did is given up. Answered once the memory is stored and the disk published. One that fails after the checkpoint lets the guest run on where it was.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |
| `snapshotId` | string | snapshot_id is the checkpoint the next start resumes, and a snapshot like any other: it is listed, it can be restored by name, and deleting it is what makes the workspace start from its disk instead. |

## UndeleteWorkspace

`POST /spin.v1.WorkspaceService/UndeleteWorkspace`

Take a workspace out of the trash, stopped, with its disk and snapshots as they were.

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string | The workspace's id: a name is not unique among the ones in the trash. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## UnexposePort

`POST /spin.v1.WorkspaceService/UnexposePort`

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `port` | integer (int32) |  |

**Response**

No fields: `{}`.

## UpdateWorkspace

`POST /spin.v1.WorkspaceService/UpdateWorkspace`

Update workspace properties (name, env)

**Request**

| Field | Type | What it is |
|---|---|---|
| `id` | string | Required: workspace ID or name |
| `name` | string | Optional: new name |
| `env` | map of string to string | Optional: replace environment variables |
| `wakeOnAccess` | boolean or null | Note: resource changes require stop/start Optional: Workspace.wake_on_access |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |

## WaitSnapshot

`POST /spin.v1.WorkspaceService/WaitSnapshot`

Wait for a snapshot to be published: until then it cannot be restored, cloned or diffed.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `snapshotId` | string |  |
| `timeoutSeconds` | integer (int32) | At most 600; zero is 300. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `snapshot` | [Snapshot](https://spin-stack.github.io/website/reference/api/types.md#snapshot) |  |

## WaitWorkspace

`POST /spin.v1.WorkspaceService/WaitWorkspace`

Wait for a workspace to reach a state: create, start and stop are answered once they are asked for, and this is how a caller learns they are done without polling.

**Request**

| Field | Type | What it is |
|---|---|---|
| `workspaceId` | string |  |
| `state` | [WorkspaceState](https://spin-stack.github.io/website/reference/api/types.md#workspacestate) |  |
| `timeoutSeconds` | integer (int32) | At most 600; zero is 300. |

**Response**

| Field | Type | What it is |
|---|---|---|
| `workspace` | [Workspace](https://spin-stack.github.io/website/reference/api/types.md#workspace) |  |
