# Managing and accessing the workspace Every workspace serves one HTTP API. The web console, the `zbox` CLI and `zbox mcp` are all clients of that API. You authorize them in one of two ways: through your Zerops login, or with the workspace API key. | Interface | Use it for | Signed in with | |---|---|---| | [Web console](#web-console) | Working with sandboxes in the browser | Your Zerops login (from the Zerops GUI or the CLI), or the API key | | [HTTP API](#http-api) | Your own code, services and CI | The API key | | [zbox CLI](#zbox-cli) | Your terminal, scripts and agents | Your Zerops login, or the API key | | [MCP server](#mcp-server) | AI agents that run code in throwaway sandboxes | Same as the CLI | ## API key The **workspace API key** is the service environment variable `ZBOX_WORKSPACE_API_KEY`, which Zerops generates when it creates the workspace ([environment variables](/zbox/workspace-service#environment-variables)). :::warning One key manages everything The API key is the whole authority over the workspace. There are no scopes and no per-user keys. Anyone who holds it, and any sandbox you give it to, can create, read, change and terminate every sandbox, base, template, integration and webhook of the workspace. Keep it out of sandboxes unless you want them to manage the workspace. See [isolation and security](/zbox/isolation). ::: You don't need the key when you are logged in to Zerops. The Zerops GUI and the CLI reach the workspace through Zerops, which adds the key on the way. When you need it, for a script, CI or another machine, print it: ```bash zbox workspace key ``` The CLI reads the key from the service with your Zerops login. Because the key is a sensitive variable, the CLI may ask you to confirm the access in the browser first. The API key never opens a sandbox port. Port links use one-time tickets instead ([access modes](/zbox/networking#access-modes)). ### Rotate the API key 1. Edit `ZBOX_WORKSPACE_API_KEY` of the workspace service in the Zerops GUI. 2. Reload the service. The reload applies the new key and keeps sandboxes running. Until then, only the old key works ([environment variables](/zbox/workspace-service#environment-variables)). 3. Update every script, CI secret and `zbox workspace use` that holds the old key. After the reload, the old key is refused and browsers signed in to the console with it are signed out. :::caution Logged-in access keeps the old key The Zerops GUI and a logged-in CLI reach the workspace with an access that Zerops creates with the key of that moment. An access created before the reload keeps sending the old key until it expires, an hour after its last use, and the CLI fails with `Missing or invalid Workspace API key` (exit code 6). Reopen the console from the Zerops GUI, and fetch a new access for the CLI: ```bash zbox workspace authorize ``` ::: ## Web console Each workspace serves a web console at the root of its API URL. It covers sandboxes (terminal, files, policy, snapshots, ports, sessions, events, metrics), images, integrations, activity, and the workspace itself. Open it in one of these ways: - **From the Zerops GUI**: in the workspace service's left menu, open **Web UI** to see the console embedded in Zerops, or **Open web UI in new tab**. You are signed in through Zerops, with no key. Your [role in the project](/features/rbac) decides whether you can open it. - **From the CLI**: `zbox workspace console` opens the console of the current workspace, signed in. For a workspace of your account it goes through Zerops. For one you added with `zbox workspace use`, it opens a one-time sign-in link that is valid for 60 seconds. - **Directly**: open the API URL, for example `https://zbox-6da-8080.prg1.zerops.app`, and paste the API key into the sign-in form. Signing in with the key exchanges it once for a session cookie, so the key is never stored in the browser. The session lasts 7 days. **Sign out** ends it in that browser. :::caution The console needs HTTPS The session cookie works only over HTTPS. Over the [Zerops VPN](/references/networking/vpn), the console at `http://.zerops:8080` loads, but signing in fails without an error. Use the Zerops GUI or the public API URL for the console. The API itself works over the VPN with a bearer token. ::: ## HTTP API The API URL is the Zerops subdomain of the workspace's port 8080, for example `https://zbox-6da-8080.prg1.zerops.app` ([subdomain access](/zbox/workspace-service#subdomain-access)). `zbox workspace` prints it on the `API` line. Other services in the project reach the API at `http://:8080` ([project network](/zbox/workspace-service#project-network)). Every route is under `/api/rest/`. Send the API key as a bearer token in the `Authorization` header, on every request including WebSocket upgrades. A key in the query string is refused. Only `GET /api/rest/ping` answers without the key. ```bash export ZBOX_WORKSPACE_URL=https://zbox-6da-8080.prg1.zerops.app export ZBOX_API_KEY=$(zbox workspace key) # the workspace: capacity, usage, domains, policy curl -H "Authorization: Bearer $ZBOX_API_KEY" "$ZBOX_WORKSPACE_URL/api/rest/workspace" # live sandboxes, newest first curl -H "Authorization: Bearer $ZBOX_API_KEY" "$ZBOX_WORKSPACE_URL/api/rest/sandbox-list" ``` The CLI, when logged in to Zerops, uses a different path: it calls the API through Zerops with a short-lived access token minted from your login, so no key is stored on your machine. ### OpenAPI document Each workspace serves its API reference without the key: - Swagger UI: `/api/rest/swagger/` - OpenAPI 3 document: `/api/rest/swagger/openapi.yml` The document describes the whole contract, including a few routes and fields that the beta does not act on yet. These docs describe only what works today. Generate a client from the document if you need one. There are no published SDKs. ### Operations Requests that create, park, wake, fork, snapshot, restore or terminate sandboxes, pull or build bases, or build templates answer right away with an **operation** and do the work in the background ([which verbs are operations](/zbox/lifecycle#operations)). An operation has an `id`, a `kind`, a `status` (`PENDING`, `RUNNING`, `FINISHED` or `FAILED`), a `result` when it finished and an `error` when it failed. To wait for one, long-poll it. `waitSeconds` holds the answer until the operation finishes or fails, for at most 30 seconds. If it is still running when the time is up, you get it as it is, so ask again: ```bash curl -H "Authorization: Bearer $ZBOX_API_KEY" "$ZBOX_WORKSPACE_URL/api/rest/operation/?waitSeconds=30" ``` Creating a sandbox (`POST /api/rest/sandbox`) also takes `waitSeconds` in its body. The answer then carries the finished operation and the sandbox in its final state. `GET /api/rest/operation-list` lists recent operations, newest first. The CLI waits for every operation by default. `--no-wait` prints the operation instead, `--timeout` stops waiting while the work goes on, and `zbox operation wait ` picks the wait up again. ### Idempotency keys The requests that consume capacity require an `Idempotency-Key` header: create, fork, park, wake, snapshot, restore and terminate a sandbox, pull a base and create a template. Use a new unique value for each logical request, and reuse it only when you retry that same request. - The same key with the same request within 24 hours replays the first answer. Nothing runs twice. - The same key with a different request, or while the first request is still in progress, is refused with `409 idempotencyKeyConflict`. - A request that failed releases its key, so you can retry with it. - Keys are shared by the whole workspace, not per client. ```bash curl -X POST "$ZBOX_WORKSPACE_URL/api/rest/sandbox" \ -H "Authorization: Bearer $ZBOX_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"name": "app", "baseDigest": "", "networkMode": "PROJECT_INTERNET", "waitSeconds": 30}' ``` The CLI and the console send the header for you. ### Errors Errors use the HTTP status code and one JSON shape. `code` is stable for your code to match on, and `meta[].metadata` lists problems by field: ```json {"error": {"code": "invalidUserInput", "message": "Invalid user input", "meta": [{"code": "invalidUserInput", "error": "Invalid user input", "metadata": {"networkMode": ["field is required"]}}]}} ``` For example, a wrong or missing key answers `401 unauthorized`, an unknown sandbox `404 sandboxNotFound`, and a full workspace `409 workspaceFull` ([capacity](/zbox/workspace-settings#capacity)). ## zbox CLI Install and log in as described in [getting started](/zbox/getting-started). Every command explains itself with `--help`, and `zbox --help` lists the commands by task. The rules below hold for all of them. ### Output and exit codes On a terminal the CLI prints tables. When stdout is a pipe or a file, it prints tab-separated values with one header line. `--json` prints the API objects as they are, and `-q` (`--quiet`) prints only ids, one per line. Progress and hints go to stderr. Under `--json`, an error is one JSON object on stderr. ```bash id=$(zbox new -q --template dev) zbox get "$id" --json ``` | Exit code | Meaning | |---|---| | 0 | OK | | 1 | Failed, for example a failed operation | | 2 | Wrong usage: bad flags or arguments, a request the API refused as invalid, a missing `--yes` | | 3 | Not found | | 4 | Conflict: the sandbox's state does not allow it, a name is taken, an operation is in progress | | 5 | Timed out waiting, the work may go on | | 6 | Not authorized: no login, no workspace, a rejected key | | 7 | Unavailable: the workspace is not reachable or is full, retry later | | 130 | Interrupted | `zbox exec` has exit codes of its own, see [run a command](/zbox/commands#run-a-command). Commands that delete something (`remove`, `base remove`, `workspace teardown`, and so on) ask for confirmation on a terminal. Without a terminal they refuse with exit code 2 unless you pass `--yes` (`-y`). ### Several workspaces The CLI remembers the workspaces you have used and keeps one as current. Commands work on the current one. ```bash # the Box Workspace services of your Zerops account, and the ones stored here zbox workspace list # store a workspace of your account; it is reached through Zerops, no key is stored zbox workspace authorize # store a workspace outside your account by its API URL and key zbox workspace use https://box-1a2-8080.prg1.zerops.app --api-key # make another stored workspace the current one zbox workspace switch # work on another stored workspace for one command only zbox list --workspace # drop the current workspace from the CLI, nothing is deleted zbox workspace forget ``` `zbox workspace use` stores the key in the CLI's configuration file. `ZBOX_WORKSPACE=` in the environment does the same as `--workspace`. ### Scripts, CI and sandboxes These environment variables override what the CLI has stored, for scripts and CI: | Variable | What it does | |---|---| | `ZBOX_WORKSPACE_URL` and `ZBOX_API_KEY` | Work on the workspace at this API URL with this key, ignoring the stored ones. Set both or neither. | | `ZBOX_TOKEN` | A Zerops [personal access token](/references/cli#personal-access-tokens) used instead of the stored login, for commands such as `zbox setup` and `zbox workspace teardown` | | `ZBOX_CONFIG` | Path of the configuration file, `~/.config/zbox/config.json` by default | The URL and key pair is the simplest way for CI, and for a sandbox that should manage its own workspace. A sandbox reaches its workspace's API only through the public API URL, so it needs a [network mode](/zbox/networking#egress) with internet access. ```bash ZBOX_WORKSPACE_URL=https://zbox-6da-8080.prg1.zerops.app ZBOX_API_KEY= zbox list ``` ## MCP server `zbox mcp` serves the current workspace as an MCP server on stdio, so an AI agent can run code in sandboxes. Register it in Claude Code: ```bash claude mcp add zbox -- zbox mcp # spawn the sandboxes from the template "dev" claude mcp add zbox -- zbox mcp --template dev ``` Any MCP client that starts a stdio server can run the same command. It has two tools: - `run_code` runs `code` in `python` (the default), `javascript` or `bash`, in `/workspace`, and answers its stdout, stderr and exit code. The image needs `python3`, `node` or `bash` for that. Without `sandbox_id`, every call creates a fresh sandbox named `mcp-