# Overview > A dedicated Linux box in the cloud that you work on from your own machine. URL: https://prized.dev/docs ## How it works [#how-it-works] You edit and run everything on the box over SSH. Three things tie it to your laptop: * **Sync.** A folder on your machine mirrors `~/code/` on the box, both ways, .git included. * **Ports.** A dev server on the box shows up on your laptop's localhost within seconds. * **Sessions.** Your shell runs in tmux on the box, so a dropped connection stops nothing that is running. ## The pieces [#the-pieces] | Piece | What it is | | -------------- | --------------------------------------------------------------------------------------------------------------------------- | | `prized` | The CLI on your laptop (macOS, Linux, or Windows). | | `prizedd` | A background daemon on your machine: runs sync and mirrors box ports onto localhost. | | `prized-agent` | A service on the box: finds listening ports, reports vitals, lists tmux sessions. | | Dashboard | [prized.dev](https://prized.dev/dashboard): create boxes, watch their state, manage billing. Every screen is on [Dashboard](https://prized.dev/docs/dashboard). | ## Sandboxes [#sandboxes] For machines your code makes and throws away, [Sandboxes](https://prized.dev/docs/sandboxes) (beta) are microVMs created through the API or the CLI: they boot from a template in seconds, bill by the second, and fork into many live copies. Not sure which you need? [Boxes or Sandboxes?](https://prized.dev/docs/sandboxes-or-boxes) compares them. ## What's on the box [#whats-on-the-box] Ubuntu with root, plus Docker (compose and buildx), git, tmux, mosh, ripgrep, jq, the GitHub CLI, Claude Code, and Codex; Cursor's CLI installs on demand. Sizes, disks, and the full list are on [Machines](https://prized.dev/docs/machines). `prized setup` hands your agent sign-ins over from your machine, so nothing on the box needs a fresh OAuth flow ([Credentials](https://prized.dev/docs/credentials)). ## For agents [#for-agents] Every page is also plain Markdown: add `.md` to its URL ([/docs.md](https://prized.dev/docs.md)), or request the page with `Accept: text/markdown`. **Copy page**, at the top right of every page, copies it as Markdown; its menu opens the page in ChatGPT, Claude, or Cursor. * **Index.** [/llms.txt](https://prized.dev/llms.txt) lists every page with a one-line summary, in this order. * **Everything.** [/llms-full.txt](https://prized.dev/llms-full.txt) is the whole set in one file, several hundred KB: more than many fetch tools read at once, so an agent does better with the index and single pages. * **Search.** `GET /api/search?query=` returns JSON hits, each with the page URL and the matching heading or paragraph. * **Setup.** To let an agent install and set Prized up for you, paste the prompt on [Agent setup](https://prized.dev/docs/agent-setup). * **Sign-up.** `prized.dev/auth.md` is the contract an agent follows to make a new account with your email address and an emailed code, and get an API key ([Agent sign-in](https://prized.dev/docs/agent-sign-in)). ## All pages [#all-pages] Every page, grouped as in the sidebar, with a one-line summary: https://prized.dev/llms.txt. --- # Boxes or Sandboxes? > Which to start with: a box is a cloud machine you work on every day; a sandbox is a microVM your code creates and throws away. URL: https://prized.dev/docs/sandboxes-or-boxes You want a **box** if you want a cloud machine to work on: your repo synced with your laptop, dev servers on your localhost, and coding agents that keep running with the laptop closed. You want a **sandbox** if your code needs clean machines to run something in and throw away, often many at once. ## Side by side [#side-by-side] | | Box | Sandbox (beta) | | ----------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Driven by | You, from your laptop through the `prized` CLI | Your code, through the [API](https://prized.dev/docs/sandboxes-api) or `prized sandbox` | | Lives | Until you delete it | Until your code deletes it, or 24 hours at most | | Starts | Usually under a minute | Usually about five seconds | | Billed | Monthly plan: hourly at its size's rate while awake, disk only while asleep | Per second, for the vCPU, memory, and disk it holds; paused, storage only | | Sync and ports | Two-way folder sync, ports on your localhost, sessions that outlive a dropped connection | None: commands and file copies through the API or `prized sandbox` | | Fork | `prized fork` copies its disk into a new box, in minutes | Into live copies, each with every process still running | | Sizes and regions | Nano to Extra Large, in 5 regions | 1 to 32 vCPU and up to 128 GB, in `us-west-2` (Oregon) | | Best for | Your daily dev machine, and agents that run for days | Code you have not read, a clean machine per test run, one job fanned out across copies | ## Using both [#using-both] Boxes and sandboxes share one workspace: one account, one balance, one dashboard, the same API keys. * **One balance.** Box hours and sandbox seconds draw on the same credit ([Billing](https://prized.dev/docs/billing#sandboxes)). * **$30 of free credit, no card.** New workspaces get it once, for either: one box, Nano to Small, and sandboxes within the [free tier](https://prized.dev/docs/sandboxes#free-tier) (4 at once, each up to 2 vCPU, 4 GB, and 60 minutes). * **Paying in lifts the limits.** A plan lifts the box rules; a plan or a [top-up](https://prized.dev/docs/billing#topping-up) gives sandboxes the full quotas. ## Start here [#start-here] * **A box:** the [Quickstart](https://prized.dev/docs/quickstart), four steps from sign-up to your project on a box. * **Sandboxes:** the [Sandboxes quickstart](https://prized.dev/docs/sandboxes#quickstart), whose **Open Sandboxes** link signs you up with no box tour. --- # Quickstart > Four steps to a box with your project on it and your coding agent signed in: sign up, install, log in, set up. URL: https://prized.dev/docs/quickstart Prefer the terminal? `npx prized` does all four steps in one command ([Sign up from your terminal](https://prized.dev/docs/terminal-signup)). Here for sandboxes, not a box? Start from the [Sandboxes quickstart](https://prized.dev/docs/sandboxes#quickstart): its link signs you up straight into Sandboxes, with no box tour. ## 1. Sign up and create a box [#1-sign-up-and-create-a-box] Sign up at [prized.dev](https://prized.dev/sign-in). New workspaces get [$30 of free credit](https://prized.dev/docs/billing#free-credit), once, and the tour starts a box on it (Nano through Small, no card) in the region nearest you; a Small runs about nine days around the clock, and longer as it sleeps after 2 hours idle. For a bigger size, start its plan on the same step; it launches when the payment lands. Signed up on a phone? Pick your box there, then **Skip for now** on the install step opens the dashboard, whose [browser terminal](https://prized.dev/docs/dashboard#terminal) works on a phone. The install and setup wait for your computer: sign in there and the tour picks up at the install step. The box bills the credit by the hour while awake, and boxes pause when the credit runs out until you pick a plan ([Billing](https://prized.dev/docs/billing)). Add boxes later with `prized box new` or the [dashboard](https://prized.dev/docs/dashboard#new-box). ## 2. Install the CLI [#2-install-the-cli] Copy the command the onboarding tour shows you: it installs the CLI, signs it in, and connects your box, so `prized ssh` works when it finishes. The plain installers below only install. The tour waits here until your machine is signed in, then moves on by itself; there is nothing to click. On a phone it offers **Skip for now** instead (step 1). Installed with a plain command? Run `prized login` (step 3) and it moves on. macOS and Linux: ```bash curl -fsSL https://prized.dev/install.sh | sh ``` Windows, in PowerShell or Windows Terminal: ```powershell irm https://prized.dev/install.ps1 | iex ```
Platforms, install folders, options, and verification It installs `prized`, its daemon, and a managed Mutagen for sync, and [updates itself](https://prized.dev/docs/cli#updates) from signed releases. It runs on macOS (Apple Silicon and Intel), Linux (x86\_64 and arm64), and Windows 10 1809 or later (x64 and arm64); a few commands are [not on Windows yet](https://prized.dev/docs/cli#windows), and [`prized mount`](https://prized.dev/docs/mount) is macOS-only. * **macOS and Linux:** into `/usr/local/bin` when writable, else `~/.local/bin`, added to your shell's startup file; `--no-modify-path` (or `PRIZED_NO_MODIFY_PATH=1`) prints the `export` line instead. * **Windows:** into `%LOCALAPPDATA%\Prized\bin`, added to your user `PATH`, no administrator rights; options `PRIZED_VERSION`, `PRIZED_CHANNEL`, `PRIZED_INSTALL_DIR`, and `PRIZED_NO_MODIFY_PATH=1`. In WSL, use the `curl` line. * **Verification:** the installer refuses a download it cannot verify: against the release signature when [minisign](https://jedisct1.github.io/minisign/) is installed, otherwise the release's checksum file against a signature-verified digest served with the script. Pinning a release (`--version`, or `PRIZED_VERSION` on Windows) needs minisign. * **Telemetry:** the signed-in CLI reports command usage, never arguments or file contents ([turn it off](https://prized.dev/docs/cli#usage-telemetry)).
## 3. Log in, only if the installer did not [#3-log-in-only-if-the-installer-did-not] Used the tour's command? Its output shows `✓ signed in as you@…` and `✓ fig connected`: **skip to step 4**. ```bash prized login ``` Your browser opens the dashboard to make an API key for this machine; paste it back. With exactly one box on your account, login connects it; otherwise `prized setup` does ([what login stores](https://prized.dev/docs/cli#sign-in-and-set-up)). > The tour's install link works once and for 15 minutes, so running that command again reports it expired. Upgrade with `prized update`, not the install link. ## 4. Set up [#4-set-up] Run setup on your machine, in the folder you want on the box: ```bash cd ~/code/myproject # the folder you want on the box prized setup ``` It asks once, auto or manual, then repairs what its health check finds, stopping only on what it cannot fix, syncs the folder, brings your environment, and signs your coding agents in on the box. **auto** does all of it without more questions (`prized setup auto` skips the first one too); **manual** asks yes or no per step. Safe to re-run. Setup syncs the folder it runs in, so make it one project, not your home folder or your Desktop. The tour waits here too, and moves on when setup finishes. It learns that from the CLI's [usage report](https://prized.dev/docs/cli#usage-telemetry); with reporting off, the step offers an **I ran it** link after a short wait. If setup stops with an error, run `prized doctor`, which repairs what it safely can, then run setup again.
What each part of setup does Setup has four optional parts: * **Project:** Syncs the folder you run it in; auto asks first only when it has no `.git` or manifest. `prized setup myproject` creates `~/code/myproject` on the box, mirrored to `~/Prized/myproject`. `prized setup ~/src/myproject` asks whether to sync that folder in place; `--copy-once`, `--copy`, or `--clone` answer up front ([Sync](https://prized.dev/docs/sync#add-a-project)). * **Environment:** After a read-only scan, brings your shell, toolchains, CLIs, Docker images, and local databases; installs and secrets default to yes, data to no. Skipped on Windows ([Bring your environment](https://prized.dev/docs/environment)). * **Agents:** After one yes, signs the agents found on your machine (Claude Code, Codex, Cursor; `--agent chatgpt` for the ChatGPT app) in on the box over the SSH tunnel; gh and your git identity go along on their own ([Credentials](https://prized.dev/docs/credentials)). * **Session:** With one runnable agent picked, starts it in a detached tmux session and prints the reattach command; `--attach` opens it here, `--no-start` skips it. If nothing synced, the report's last line says so and gives the command; `prized sync` lists what is synced. No coding agent on your machine? Setup prints how to sign in to Claude Code on the box, or sign in locally and re-run `prized setup` to hand the sign-in over.
> In Claude Code, select text and press **c**: through `prized ssh` it lands on your clipboard ([Copy out](https://prized.dev/docs/sessions#copy-out)). Then connect any time, to a persistent tmux session on the box: ```bash prized ssh ``` `prized status` shows the box's vitals, sync state, mirrored ports, and daemon health; `prized doctor` checks everything when something is off and fixes what it can. The tour ends on these and the other commands people use most (`prized doctor`, `ssh`, `status`, `exec`, `mount`, `help`), each with a copy button. **Open the dashboard** finishes it; **Help** at the foot of the dashboard's sidebar walks you through it again ([Dashboard](https://prized.dev/docs/dashboard#layout)). ## Or let an agent do it [#or-let-an-agent-do-it] Steps 2 through 4 are one paste into your coding agent: the prompt is on [Agent setup](https://prized.dev/docs/agent-setup). --- # Sign up from your terminal > One command signs you up, starts a box on the free credit, and sets up this machine: npx prized. URL: https://prized.dev/docs/terminal-signup ## Start [#start] In the folder you want on your box: ```bash npx prized ``` It needs Node.js 18 or later on macOS, Linux, or Windows (PowerShell or Windows Terminal). No Node.js? Sign up at [prized.dev](https://prized.dev/sign-in) and follow the [quickstart](https://prized.dev/docs/quickstart). ## What it asks [#what-it-asks] | Step | What you do | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sign in | Pick GitHub or Google in the browser that opens, check the account and where it will connect, then click **Connect terminal**. Only confirm a sign-in you started, never one from a link someone sent you. New accounts get [$30 of free credit](https://prized.dev/docs/billing#free-credit). | | Your box | **Automatic** starts the biggest box the free credit runs (a Small: 4 vCPU, 16 GB, about nine days) or uses the box you have; **Manual** lists every size and how long your credit runs it. | | Where you work | Claude desktop, the ChatGPT app, Cursor, or the terminal (Claude Code, Codex, Cursor Agent, or a plain shell). | ## Then this machine is set up [#then-this-machine-is-set-up] The CLI installs and signs in with nothing to paste, then asks which folder to sync (the one you ran it in, a recent git checkout, or a new empty project). **Automatic** brings your tools and packages but asks before copying anything secret (`.env` files, API keys, your agent's sign-in); **Manual** asks about each part, like [`prized setup`](https://prized.dev/docs/quickstart#4-set-up). In the numbered folder list, long labels are shortened to fit your terminal; the selected folder’s full path is unchanged. If a shortened label is ambiguous, type the full path instead. Then it sets up where you work: | You work in | What happens | | -------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Claude desktop | Adds your box to Claude desktop and opens a Code session in your synced folder ([Claude Desktop remote](https://prized.dev/docs/claude-desktop)). | | ChatGPT app | Signs Codex in on the box and adds the box and synced folder to the app, connected ([Codex remote](https://prized.dev/docs/codex)). | | Cursor | Opens your synced folder on the box over Remote-SSH. | | Terminal | Starts your agent in a tmux session on the box and leaves you in it; it keeps running when you close the laptop. | Setting up an app needs macOS and the app installed; otherwise the command ends with the steps to add your box by hand. The box is on the [dashboard](https://prized.dev/dashboard) too, with nothing left to set up there. Already signed in on this machine? Run `prized setup` in the folder you want on your box instead. ## Troubleshooting [#troubleshooting] | Symptom | What to do | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | The browser did not open | Open the whole link the terminal shows in a browser on this machine; another device cannot finish the sign-in. A page asking for the whole link means it was cut short: copy all of it. | | "This sign-in link has expired." | A sign-in lasts 15 minutes. Run `npx prized` again; an existing account just signs in again. | | "Too many tries." | Too many wrong or expired attempts. Wait a few minutes, then run `npx prized` again. | | It ends with a `prized setup` line | Run the whole line in the folder you want to sync: with `--signup` and its ID it finishes the steps above. After plain `prized setup`, add the box to your app by hand ([Claude desktop](https://prized.dev/docs/claude-desktop), [the ChatGPT app](https://prized.dev/docs/codex)). | | Using ssh with a phone password | That password is for the ChatGPT app's SSH form ([From your phone](https://prized.dev/docs/codex#from-your-phone)); from a computer, use `prized ssh`. | --- # Agent setup > Paste one prompt into Claude Code or Codex on your machine; the agent installs the CLI, runs prized setup, and asks before putting your project or your credentials on the box. URL: https://prized.dev/docs/agent-setup ## One prompt [#one-prompt] Create the box in the [dashboard](https://prized.dev/dashboard), then paste this into your agent on your machine, in the project you want on the box. It covers steps 2 through 4 of the [Quickstart](https://prized.dev/docs/quickstart). ```text title="Prompt" Set up Prized on this machine and, if I confirm each part, put this project and my coding agents on my box. Prized is a cloud dev box that syncs folders with this machine; the docs live at https://prized.dev/docs. https://prized.dev/llms.txt lists every page; fetch the ones you need as Markdown by adding .md to a page URL (https://prized.dev/docs/quickstart.md). Rules: copy, never move or delete; never print credentials or read ~/.config/prized; ask me before installing software, before transferring any credential, and before doing anything that could cost money. Non-interactive prized commands accept --json and print exactly one JSON object with an "ok" field; error codes and exit codes are stable and listed at https://prized.dev/docs/cli.md. 1. Install: if `prized version` fails, ask me, then run this and re-check (on Windows, in PowerShell: irm https://prized.dev/install.ps1 | iex): curl -fsSL https://prized.dev/install.sh | sh 2. Sign in: if prized commands fail with code AUTH or a "prized login" hint, ask me whether I have a Prized account. If I do, stop and ask me to run `prized login` in another terminal, or to paste an API key from the dashboard for `prized login --token `. If I don't, ask for my email address and follow https://prized.dev/auth.md: Prized emails me a 6-digit code, I give it to you, and you get an API key for my new account. Then run `prized login --token ` and never print the key. If that page is not available, stop and ask me to run `prized login` in another terminal. 3. Everything else is one command, safe to re-run: prized setup --json --non-interactive It never prompts; each consent-gated step runs only when its grant flag is present and is reported as skipped otherwise. Ask me before adding each flag, and only pass the ones I say yes to: - `--project `: sync this folder with the box in place; the folder itself becomes the live side and the box wins conflicts. Ask me first. Two alternatives are their own distinct grants, so relay my choice exactly: `--copy` syncs a copy under ~/Prized and leaves the folder alone; `--clone` clones the repo onto the box over git with no live mirror, the right choice for a repo I actively work in elsewhere. Say which one I picked in your report; if I say no to all of them, run setup without --project. - `--agent claude|codex|cursor|chatgpt` (repeatable): hand that agent's sign-in and MCP config to the box. Ask me per agent. - `--install-cursor`: run Cursor's installer on the box. Its own yes. - `--gh`: copy my GitHub CLI token and git identity (an interactive setup does this on its own; your non-interactive run needs the flag). Its own yes. - `--env `: bring parts of my local environment to the box without asking: shell, python, node, tools, clis, docker, services (comma-separated). Run `prized env plan --json` first and read me the groups and items it lists; pass only the groups I say yes to. - `--env-secrets`: copy my secrets: the project's `.env` files, secret-looking env vars, and CLI sign-in files (aws, gcloud, kubectl, ...). Its own yes. - `--env-data`: copy database contents, Docker volumes, and local-only images; the plan shows sizes. Its own yes. If setup fails with exit 5 naming multiple candidate boxes, ask me which one and re-run with `--box `; if it finds no box on the account, ask me to create one in the dashboard. Read steps[] in the JSON: "skipped" entries name what was declined or not granted, "failed" entries carry the hint. 4. Claude Code sign-in copies from this machine when I am signed in here, so `--agent claude` works from your run. If the claude row comes back manual with the note "no Claude Code sign-in on this machine to copy", this machine is not signed in to Claude Code: no credentials, settings, or MCP configuration were copied, and any existing box sign-in was left alone. A local sign-in that cannot be read also leaves the box unchanged. Relay the row's steps to me (`prized ssh `, then `claude`, then `/login` inside it); I sign in once on the box and it persists there. 5. Verify: `prized status --json` reports ok; if we set up the project as a sync (not `--clone`; a clone has no sync session), also that `prized sync flush ` completes. Check `prized agents ls --json` shows each chosen agent installed and signed in on the box. 6. Report: the box name, the project path on both sides (or the clone path on the box, or that I chose to keep this repo local), which project mode I granted, each agent's handoff outcome, and the reattach command for any session setup started. ``` ## What the agent asks you [#what-the-agent-asks-you] | It asks about | What happens | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sign-in | Run `prized login` yourself, or give the agent a key from the dashboard. New to Prized? The agent can make your account with your email address and the 6-digit code Prized emails you ([Agent sign-in](https://prized.dev/docs/agent-sign-in)); give the code only to the agent you are using. | | The project | In place, `--copy`, or `--clone` ([Sync](https://prized.dev/docs/sync#add-a-project) compares them); no to all three still leaves a signed-in CLI and a healthy box. | | Your environment | The groups `prized env plan --json` lists; the ones you accept go in `--env`, and secrets and data are their own yes ([Bring your environment](https://prized.dev/docs/environment)). | | Each credential | One flag per yes (`--agent `, `--gh`, `--install-cursor`); the transfer runs over the SSH tunnel and the agent never handles a value ([Credentials](https://prized.dev/docs/credentials)). | > Agents you picked before are saved as `[agents].preferred`, so a later run hands them off without `--agent`; `--no-agents` runs on nothing but its own flags. Signing in to Claude Code on the box? Select the sign-in link and press **c** to copy it through `prized ssh` ([Copy out](https://prized.dev/docs/sessions#copy-out)). To run agents on the box afterwards, see [Agents](https://prized.dev/docs/agents). --- # Agent sign-in > New to Prized? Your coding agent makes your account with your email address and a 6-digit code, and gets an API key. URL: https://prized.dev/docs/agent-sign-in ## Start [#start] Tell your agent: ```text title="Prompt" Sign me up for Prized with my email address by following https://prized.dev/auth.md, then start a box for me. ``` Prized emails you a 6-digit code; give it to your agent. The code makes your account, with the [free credit](https://prized.dev/docs/billing#free-credit) and no card, and the agent gets an API key for the [API](https://prized.dev/docs/api) and the CLI (`prized login --url https://api.prized.dev --token-stdin`). Already on Prized? Agent sign-in only makes new accounts. Make your agent a key under **Settings → API keys** instead. > [!WARNING] > Share the code only if you just asked an app you trust to create your Prized account. Prized has not verified which app made the request. Sharing the code gives the requester an API key that can create and use boxes and sandboxes in your new account. ## What happens [#what-happens] | Step | What happens | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | The email | "Your Prized agent code": the code, a warning that the requester is unverified, what sharing the code permits, and where the request came from. It lasts 15 minutes and allows five tries. | | The account | Named after you when the agent knows your name, in a workspace of its own. To join your company's workspace, open its [invite link](https://prized.dev/docs/teams#add-your-team). | | Later sign-ins | Google or GitHub on the same address opens the same account. | ## The key [#the-key] * **The agent's.** Listed under **Settings → API keys** by the agent's self-reported name, marked as an agent's key. The name is a label, not a verified identity. Revoke it there; the agent can also revoke it itself. * **Limited.** It starts, stops and uses boxes and reads the workspace; no secrets ([What a key can do](https://prized.dev/docs/workspaces#what-a-key-can-do)). * **Expires.** After 90 days; make the agent a new one under **Settings → API keys**. **Sandboxes.** The key also creates and uses [sandboxes](https://prized.dev/docs/sandboxes). On the free credit they run within the [free tier](https://prized.dev/docs/sandboxes#free-tier); a top-up or a plan lifts it. Each sandbox it creates is a `sandbox.create` row on the [audit log](https://prized.dev/docs/teams#what-is-recorded) naming the key, as each box it creates is a `box.create` row. ## Boxes and billing [#boxes-and-billing] The agent can start a Small on the free credit right away. The credit comes once per inbox: `you+work@gmail.com` is `you@gmail.com`. Medium and larger need a plan, which you start under **Settings → Billing**. Agents read prices from [pricing](https://prized.dev/pricing), never from memory. ## For agent builders [#for-agent-builders] Save the verified key with `prized login --url https://api.prized.dev --token-stdin`: start the CLI with your process runner, write the key plus a newline directly to its standard input, then close that input. Never embed it in process arguments, shell command strings (including inline scripts and here-documents), environment variables, or tool logs. Linux process arguments may be readable by other local users. If your tool cannot supply standard input without logging the key, ask the person to run the interactive CLI login instead. `prized.dev/auth.md` is the contract. Call the API's own origin, `https://api.prized.dev`. Keep the explicit `--url` when logging the CLI in: without it, the CLI sends the key to its configured control plane (or the production API by default) before saving it. For a preview or local deployment, use the API origin in that deployment's `/auth.md`, not the web origin. | Route | What it does | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `POST /api/v1/agent/sign-up` | `{email, name?, personName?}`, no credential: the key, unverified, and the emailed code. The same answer whether or not the address has an account. | | `POST /api/v1/agent/verify` | `{code}` with the key as bearer: the account and a working key, or `409 sign_in_required` if the address already had an account. | | `DELETE /api/v1/agent/key` | The key as bearer: it stops working. | While a code is live, signing up again answers `409 code_pending`. The email never includes the caller-supplied agent name, even for well-known tools. A name does not authenticate the requester. Throwaway and placeholder domains are refused. Limits: 10 sign-ups and 5 codes an hour per network, 5 codes a day per inbox, and a few credited new accounts a day per network. ## Troubleshooting [#troubleshooting] | Symptom | What to do | | ------------------------------ | ------------------------------------------------------------------------------------------------ | | No email | Check spam. A new code goes out only after the last one expired, five a day at most. | | `code_expired` | Older than 15 minutes, or tried five times. The agent asks for a new one. | | `sign_in_required` | You already have an account: sign in at prized.dev and make a key under **Settings → API keys**. | | The agent cannot reach auth.md | Run `prized login` yourself, or give the agent a key from **Settings → API keys**. | | A key you did not expect | Revoke it under **Settings → API keys**. | --- # Bring your environment > prized setup scans your machine, asks a few yes or no questions, and reproduces the parts you pick on the box: shell, toolchains, CLIs, Docker images, databases, and env vars. URL: https://prized.dev/docs/environment ## What it does [#what-it-does] ```bash prized setup # ✓ fig ready # ✓ project ~/src/app ⇄ fig:~/code/app # bring Bring your dev environment to the box? # shell aliases + exports · zsh as the login shell · ~/.tmux.conf # python Python 3.12 · ruff · pre-commit · project deps (uv) # node Node 22.4.0 · pnpm · typescript # docker postgres:16 · redis:7 · compose up: app # services postgres 16 on :5432 · redis on :6379 # [Y/n] n = choose group by group ``` The step runs inside `prized setup`, between the project and agents steps, and its scan is read-only. Whatever did not carry is named with its reason (`✓ env 41 moved · 2 not carried (~/.vimrc: contains a key-shaped value)`); re-running is safe, as items already there report `already on the box`. `prized env scan` shows the scan, `prized env plan` the questions without applying anything, and `prized env migrate` runs the step outside setup; `prized setup --dry-run` prints the whole run and changes nothing. ## The questions [#the-questions] The install groups are one question: `y` or Enter takes them all, `n` asks group by group. Secrets are one question listing exactly what would move; data is its own question. | Group | Default | What it brings | | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | shell | yes | Aliases, functions, and non-secret exports into `~/.config/prized/shell.sh`; zsh with oh-my-zsh, powerlevel10k, or starship if you use them; `.tmux.conf`, `.vimrc`, nvim config. | | python | yes | Your Python versions via `uv` (or only the one the project pins), pipx and `uv tool` packages, and the project's dependencies from its lockfile. | | node | yes | Your Node versions via `fnm` (or only the one the project pins), global npm packages, and pnpm or yarn through corepack. | | tools | yes | Go and Rust at the same versions, `cargo install` binaries, Homebrew formulae as apt packages or Linux bottles (no bottle: `not carried`), and executables in `~/bin`, `~/.local/bin`, and `~/go/bin`, reinstalled from their origin. A Go tool the box cannot download (a private module, or a build of uncommitted or unpushed code) or whose code excludes Linux is `not carried`, with the reason; one that fails to compile on Linux fails its item. | | clis | yes | Developer CLIs on your PATH (aws, gcloud, az, kubectl, helm, terraform, flyctl, vercel, wrangler, supabase, stripe, ngrok, and more); one signed in through the macOS Keychain is `not carried`, with the login command for the box. | | docker | yes | Registry images pulled, project Dockerfiles rebuilt, and a running compose project started after the secrets step (so its `env_file` exists). Another compose project's images and volumes are `not carried`; set it up from its own folder. | | services | yes | Local Postgres, MySQL, Redis, MongoDB, and similar, as containers at the same major version and port, with data in a named volume and the client installed. A port already taken on the box fails the item, with the reason. | | data | no | Database contents, named Docker volumes, and local-only images, streamed over the tunnel; sizes are shown first. The copy uses the Docker engine and the `docker`, `pg_dump`, and `gzip` binaries the scan found, never a changed `DOCKER_*` or one on `PATH`. | | secrets | yes | The project's `.env` files (below), secret-looking env vars from your shell files, and CLI sign-in files (`~/.aws`, `~/.config/gcloud`, `~/.kube/config`, ...), written 0600, values never shown. With [AWS access](https://prized.dev/docs/aws) connected, the box needs no `~/.aws` credentials. | The box is amd64, so an arm64-only image is rebuilt or re-pulled, never streamed. Conda, Ruby and Java toolchains, casks, and devcontainers have no recipe and are listed as `not carried`, to set up by hand. ### `.env` files [#env-files] A sync session `prized setup` creates ignores `.env` and `.env.*` at every depth (`.env.example` and `.env.sample` still sync), so syncing alone never moves a credential file. The secrets question lists each one found, a monorepo's `apps/web/.env.local` included, and a yes (the default) copies it to the same relative path on the box, 0600; under `--non-interactive`, `-y` or `--env-secrets` grants it. A symlink to another `.env` inside the project is recreated as a symlink; a hard link, a symlink leaving the project, or a file you cannot read is refused as not carried. > A project added by hand with `prized sync add`, or synced before setup ignored `.env` files, still mirrors them; add `.env` to its `extra_ignores` to stop that. On a [restricted box](https://prized.dev/docs/environments#safe-for-third-parties), `prized setup` adds the ignores to every sync project itself. Once a session ignores `.env` files, `--unignore` brings back only `.env.example` and `.env.sample`. ## Flags [#flags] | Flag | Effect | | ------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `--env shell,python,...` | Bring these install groups without asking (the grant for `--non-interactive`; `-y` covers them too). | | `--env-secrets` | Copy your secrets without asking: `.env` files, env-var values, CLI sign-ins (`-y` covers this too). | | `--env-data` | Copy database contents, volumes, and local-only images. Never covered by `-y`. | | `--no-env` | Skip the step. | | `--no-advisor` | Never contact the advisor; the built-in rules ask the questions. | | `--env-workers N` | How many items install at once (default: up to 8); `1` installs them one at a time, in order. | | `--attach`, `--no-attach` | Open the box session in this terminal when setup finishes; the default (`--no-attach`) prints the reattach command. | ## The advisor [#the-advisor] The scan is deterministic. Before asking, setup sends the inventory and your project's manifests (README, Makefile, package.json, compose file, version pins) to Prized's advisor, a bounded Claude pass that fits the questions to the project. It sees names, versions, and sizes only: secret values are redacted before anything leaves your machine, credential files are never read, and paths are relative (`.` for the project, `~` for your home). It cannot run anything, picks only from the rules' recipe catalog, and cannot add a secrets or data item the scan did not find. When it is unreachable, the rules ask the questions; `--no-advisor` or `PRIZED_ADVISOR=off` skips it. ## What never moves [#what-never-moves] | Never moves | Detail | | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Private keys and vaults | SSH private keys, GPG keys, the macOS Keychain, browser profiles, 1Password data; the report says when commit signing needs a box key. | | Secret values, into any output | The JSON envelope, the terminal, telemetry, and the advisor carry names only. | | An export that is not one literal assignment on its own line | A computed value (`$(...)`, backticks), a second command, pipe, redirect, or open quote on the line, a `PATH` edit, or a secret-looking name sharing a line stays home as `not carried`; give the value its own line to carry it. | | Anything through the control plane | Every transfer goes over your SSH tunnel straight to the box ([Credentials](https://prized.dev/docs/credentials)). | ## On the box afterwards [#on-the-box-afterwards] | Path | What it is | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `~/.config/prized/shell.sh` | Your aliases, functions, exports, and the toolchains' PATH hooks; sourced from `~/.profile`, `~/.bashrc`, and `~/.zshrc`. | | `~/.config/prized/env.secret` | The env-var values you chose to move, 0600, sourced by `shell.sh`. | | `~/.config/prized/welcome.txt` | The welcome block setup printed: what moved, what was left. | | `prized-postgres`, `prized-redis`, ... | Service containers, `--restart unless-stopped`, data in `prized--data` volumes. | Services listen on `127.0.0.1` on the box; `prized ports` mirrors them to your machine like any other box port. --- # Keep Claude Code running with your laptop closed > Why closing the lid stops Claude Code, the ways to keep it working, and how to run it on a box that stays on while your laptop sleeps, with what it costs. URL: https://prized.dev/docs/keep-claude-code-running A sleeping laptop runs nothing, so closing the lid stops Claude Code mid-task. Over SSH, the lid drops the connection, and Claude Code ends with it unless it runs inside tmux. To keep working, it needs a persistent session on a machine that stays on: * **tmux on a server you run.** You maintain the machine and move your code there with git. * **Claude Code cloud sessions** (`claude --cloud`, on Claude Pro, Max and Team, and Enterprise seats with Claude Code). A VM that Anthropic runs clones your GitHub repository at the pushed branch ([Anthropic's docs](https://code.claude.com/docs/en/claude-code-on-the-web)). * **A Prized box.** A Linux machine that stays on, with your project folder synced both ways and its ports on your localhost. The steps follow. ## 1. Put your project on a box [#1-put-your-project-on-a-box] ```bash cd ~/code/myproject npx prized ``` It signs you up (GitHub or Google), starts a box on $30 of free credit with no card, and syncs this folder with `~/code/myproject` on the box. Pick **Terminal**, then **Claude Code**: after asking, it copies your Claude Code sign-in to the box and starts Claude Code there, in a tmux session named `claude` ([Sign up from your terminal](https://prized.dev/docs/terminal-signup)). Already signed in on this machine? Run `prized setup` in the folder instead. ## 2. Close the lid [#2-close-the-lid] Give Claude Code its task, then close the terminal or the laptop. The session belongs to the box, so Claude Code keeps working, and its edits sync to your laptop when it wakes ([Sync](https://prized.dev/docs/sync#offline)). If the session is gone, this starts it again: ```bash prized agents run claude --attach --dir code/myproject ``` ## 3. Check on it from anywhere [#3-check-on-it-from-anywhere] * **From a computer:** `prized ssh -s claude`. On another computer, install the CLI and run `prized login` first. * **From your phone or any browser:** open the box on the [dashboard](https://prized.dev/dashboard). The arrow beside **Open terminal** lists **Claude Code**, which opens the box's terminal in the `claude` session. ## 4. Get notified [#4-get-notified] Type `/remote-control` in the session to follow it from the Claude app, then turn on push notifications in `/config`; this needs a Claude subscription sign-in, not an API key ([Anthropic's docs](https://code.claude.com/docs/en/remote-control)). On your computer, agents you run in [herdr](https://prized.dev/docs/herdr) post a notification when they finish or need you. ## Or hand it a task and walk away [#or-hand-it-a-task-and-walk-away] ```bash prized prompt --provider claude --cwd code/myproject --auto --detach "run the test suite and fix what fails" ``` This runs Claude Code headless on the box and returns at once; `prized events --follow` follows the run. `--auto` lets it run commands without asking ([Prompt an agent remotely](https://prized.dev/docs/remote-agents#approvals)). A headless run counts as use only while it keeps the CPU or network busy, so one waiting on the model can be paused mid-task. Before a long run, switch auto-pause off for the box with `prized box auto-pause BOX off` ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)). ## What it costs [#what-it-costs] | | Small (4 vCPU, 16 GB) | Nano (2 vCPU, 2 GB) | | --------------------------------------------------------- | --------------------- | ------------------- | | Awake, per hour | $0.137 | $0.014 | | A 10 hour run overnight | $1.37 | $0.14 | | How long $30 of free credit lasts, awake around the clock | About nine days | About three months | Asleep, a box pays only for its disk ([Billing](https://prized.dev/docs/billing)). On the free credit, a box sleeps after 2 hours idle, never in its first 24 hours; Claude Code's output in its tmux session counts as use, so the box stays up while it prints ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)). A Small keeps Claude Code's session through a pause, and `prized box pause` sleeps it at once ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). --- # Run Claude Code and Codex on a remote dev box > Run Claude Code or Codex on a cloud Linux box and keep working locally: your project synced both ways, dev servers on localhost, or the box in Cursor or VS Code. URL: https://prized.dev/docs/remote-dev-box On a remote box, the agent's installs, builds, tests and dev servers run on the box's CPU instead of your laptop's, and keep running while the laptop sleeps. Prized makes the box feel local: your project folder syncs both ways, and the box's ports open on your localhost. ## 1. Create a box and sync your project [#1-create-a-box-and-sync-your-project] ```bash cd ~/code/myproject npx prized ``` It signs you up, starts a box on $30 of free credit with no card, and syncs this folder with `~/code/myproject` on the box; edits travel both ways in under a second ([Sync](https://prized.dev/docs/sync)). Pick **Terminal**, then **Claude Code** or **Codex**: after asking, it signs that agent in on the box, and `prized agents handoff codex` (or `claude`) adds the other later ([Credentials](https://prized.dev/docs/credentials)). Already signed in? Run `prized setup` in the folder instead ([Quickstart](https://prized.dev/docs/quickstart#4-set-up)). ## 2. Start the agent [#2-start-the-agent] ```bash prized agents run claude --session claude-myproject --dir code/myproject ``` This starts Claude Code in a tmux session named `claude-myproject`, in `~/code/myproject` (`--dir` is relative to your home on the box); `prized agents run codex` with its own session name does the same for Codex. Naming the session and folder keeps each project's agent apart when the box holds several. Attach with `prized ssh -s claude-myproject` and detach with Ctrl-b, then d; the agent keeps working either way ([Agents](https://prized.dev/docs/agents)). ## 3. Open the dev server on localhost [#3-open-the-dev-server-on-localhost] ```bash title="you@box ~/code/myproject" npm run dev # ready on http://localhost:3000 ``` About two seconds later, `localhost:3000` opens on your laptop, websockets included. Most ports from 3000 to 9999 mirror on their own; `prized ports ls` lists them, and `prized proxy` forwards one outside that range ([Ports](https://prized.dev/docs/ports#limits)). ## Or edit over SSH [#or-edit-over-ssh] Setup adds the box to your SSH config under its name, so **Remote-SSH: Connect to Host** in Cursor, or in VS Code with its Remote - SSH extension, connects with nothing else to set up ([steps](https://prized.dev/docs/cursor-remote-ssh)). Open `~/code/myproject`: the editor then works on the box's copy, and its terminal runs on the box. Sync gives every tool on your laptop a real folder; Remote-SSH works without one ([Sessions](https://prized.dev/docs/sessions#editors-and-other-tools)). ## Several agents on one box [#several-agents-on-one-box] ```bash prized box resize mango Medium ``` Use your box's name in place of `mango`; `prized box ls` lists them. A Medium (8 vCPU, 32 GB) needs a plan. Give each agent its own git worktree and session: [Run multiple Claude Code and Codex agents in parallel](https://prized.dev/docs/parallel-agents) has the steps. ## What it costs [#what-it-costs] A Small (4 vCPU, 16 GB) is $0.137 an hour awake, and a Medium (8 vCPU, 32 GB) $0.342; asleep, a box pays only for its disk. The $30 of free credit runs a Small for about nine days around the clock ([Billing](https://prized.dev/docs/billing#sizes-and-rates)). --- # Use Cursor or VS Code on a cloud dev box with Remote-SSH > Open a cloud Linux box in Cursor or VS Code over Remote-SSH, with no keys or hosts to set up, and keep Claude Code or Codex running after the editor closes. URL: https://prized.dev/docs/cursor-remote-ssh Prized adds your box to your SSH config, so Cursor and VS Code open it like any SSH host: the window stays on your laptop, and files, terminals and extensions run on the box. ## 1. Create a box [#1-create-a-box] ```bash cd ~/code/myproject npx prized ``` It signs you up, starts a box on $30 of free credit with no card, and syncs this folder with `~/code/myproject` on the box. Pick **Cursor** and, on macOS, it opens that folder on the box for you. Already signed in? Run `prized setup` in the folder instead. Setup writes a `Host` entry for the box to `~/.ssh/config.d/prized` and an `Include` line at the top of `~/.ssh/config`. There are no keys to copy, and the box has no inbound SSH port ([Security](https://prized.dev/docs/security)). ## 2. Check that ssh reaches it [#2-check-that-ssh-reaches-it] ```bash ssh mango echo connected ``` Use your box's name for `mango` (`prized box ls` lists them). If it prints `connected`, the editor will connect too: Remote-SSH runs the same `ssh`. ## 3. Connect the editor [#3-connect-the-editor] * **Cursor:** open the Command Palette (Cmd-Shift-P, or Ctrl-Shift-P), run **Remote-SSH: Connect to Host**, and pick `mango`. If the command is missing, install the **Remote - SSH** extension from Cursor's Extensions view. * **VS Code:** install Microsoft's **Remote - SSH** extension, then run the same command. * **Asked for the platform:** pick **Linux**. Then **File > Open Folder** and open `~/code/myproject`. The editor's terminal, search and extensions now run on the box. ## 4. Keep agents running after the editor closes [#4-keep-agents-running-after-the-editor-closes] The editor's terminal ends with its window, or when the laptop sleeps. For an agent that keeps working, start tmux in that terminal: ```bash title="you@mango ~/code/myproject" tmux new -A -s claude ``` Run `claude` (or `codex`) inside it, then close the editor or the laptop. The same command reattaches, as does `prized ssh mango -s claude` from any terminal ([Sessions](https://prized.dev/docs/sessions)). ## If the box is asleep [#if-the-box-is-asleep] Remote-SSH, plain `ssh` and sync do not wake a paused box: the editor fails to connect, and its log says the box is not running. Wake it, then run **Developer: Reload Window**: ```bash prized box resume mango --wait ``` `prized ssh mango` wakes it too. On the free credit, a box pauses after 2 hours idle, but never in its first 24 hours; on a plan, only if you set auto-pause ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)). ## Sync or Remote-SSH [#sync-or-remote-ssh] Remote-SSH runs the editor's tools on the box, which must be awake. [Sync](https://prized.dev/docs/sync) keeps the folder on your laptop for any local editor, offline too. Both work on the same project at once; when both sides change a file, the box's copy wins by default ([Conflicts](https://prized.dev/docs/sync#conflicts-the-box-wins)). ## Troubleshooting [#troubleshooting] | Symptom | What to do | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `Could not resolve hostname mango` | The `Include` line is missing from `~/.ssh/config`: `prized doctor` adds it ([Troubleshooting](https://prized.dev/docs/troubleshooting)). | | `mango is paused because your free credit ran out` | Nothing was deleted. Pick a plan at the link it prints to start the box again ([Running out](https://prized.dev/docs/billing#running-out)). | | `"mango" is not the box this machine knew by that name` | The box was recreated, or another box took its name: `prized box rebind mango`. | ## Related [#related] * [Run Claude Code and Codex on a remote dev box](https://prized.dev/docs/remote-dev-box) * [Keep Claude Code running with your laptop closed](https://prized.dev/docs/keep-claude-code-running) * [Run multiple agents in parallel](https://prized.dev/docs/parallel-agents) --- # Run multiple Claude Code and Codex agents in parallel > Run several coding agents at once without slowing your laptop: a git worktree and a session per agent, on one bigger box or a box each, with what each costs. URL: https://prized.dev/docs/parallel-agents Each agent runs its own installs, builds and tests, so three agents on a laptop share one CPU and one battery with your editor. On a box, each agent gets its own git worktree and tmux session, and they keep working with the lid closed. ## 1. Add a worktree per task [#1-add-a-worktree-per-task] ```bash prized ssh ``` Then, on the box: ```bash title="you@box" cd ~/code/myproject git worktree add ../myproject-auth -b auth git worktree add ../myproject-billing -b billing ``` Each worktree is its own folder and branch with tracked files only, so install dependencies in it. Worktrees beside the project stay on the box; only `~/code/myproject` syncs to your laptop ([Sync](https://prized.dev/docs/sync#add-a-project)). ## 2. Start an agent in each [#2-start-an-agent-in-each] ```bash prized agents run claude --session claude-auth --dir code/myproject-auth prized agents run codex --session codex-billing --dir code/myproject-billing ``` `--dir` is relative to your home on the box. Attach with `prized ssh -s claude-auth` to give an agent its task, and detach with Ctrl-b, then d; to skip attaching, hand it the task with [`prized prompt`](https://prized.dev/docs/remote-agents). Give each dev server its own port, such as 3001 and 3002 ([Ports](https://prized.dev/docs/ports)). ## 3. Watch them [#3-watch-them] `prized ssh --ls` lists the sessions. For one view of every agent, `prized herdr` opens [herdr](https://prized.dev/docs/herdr), which shows each agent started in its tabs as working, blocked, idle or done, and notifies you when one needs you. ## 4. Review and merge [#4-review-and-merge] ```bash title="you@box ~/code/myproject-auth" git diff git add -A git commit -m "Add rate limiting to the login endpoint" git push -u origin auth ``` `gh` on the box is signed in if it was on your laptop at setup, so `gh pr create` works there. Once merged, `git worktree remove ../myproject-auth` cleans up. ## A box per agent [#a-box-per-agent] Separate boxes keep one agent's build from slowing another, and each box pauses on its own; this needs a plan, since the free credit runs one box at a time. Fork the box you set up (here `mango`), project and tools included, then connect the copy and start its agent: ```bash prized fork mango --name mango-2 --wait prized init --box mango-2 prized --box mango-2 agents run claude --dir code/myproject ``` A fork takes minutes; to start many boxes from one point, take a [snapshot](https://prized.dev/docs/snapshots#take-a-snapshot) once and restore it into each. Give each box's dev server its own port, since the same port on two boxes collides on your laptop ([Several boxes](https://prized.dev/docs/boxes#several-boxes)). ## What it costs [#what-it-costs] For the same total CPU and memory, several Smalls cost less per hour awake than one bigger box: | Setup | In total | Awake, per hour | | ----------- | -------------- | --------------- | | One Medium | 8 vCPU, 32 GB | $0.342 | | Two Smalls | 8 vCPU, 32 GB | $0.274 | | One Large | 16 vCPU, 64 GB | $0.685 | | Four Smalls | 16 vCPU, 64 GB | $0.548 | An eight hour day of four agents is $4.38 on four Smalls, or $5.48 on one Large. On the $30 of free credit, one Small runs your agents side by side at $0.137 an hour, about nine days around the clock ([Billing](https://prized.dev/docs/billing)). --- # Run Superset's agents on a Prized box > Add a Prized box as a Superset host, so Superset's parallel agents run on a cloud Linux machine that keeps working while your laptop sleeps, with a size for each number of agents. URL: https://prized.dev/docs/superset [Superset](https://superset.sh) runs many coding agents side by side, each in its own git worktree. With a Prized box as its host, they build and test on the box instead of your laptop, and keep going with the lid closed. ## 1. Start a box [#1-start-a-box] ```bash npx prized ``` It signs you up, starts a box on $30 of free credit with no card, and sets up this machine, including an entry for the box in your SSH config ([Quickstart](https://prized.dev/docs/quickstart)). Already signed in? Use the box you set up, or start another on a plan with `prized box new mango --wait`, then `prized init --box mango`. ## 2. Check plain ssh [#2-check-plain-ssh] Superset reaches the box through your own `ssh`. Use your box's name in place of `mango` (`prized box ls` lists them): ```bash ssh mango echo ok # ok ``` `Could not resolve hostname` means your ssh skipped that entry: `prized doctor` says why and repairs what it can ([Troubleshooting](https://prized.dev/docs/troubleshooting)). ## 3. Sign the agents in on the box [#3-sign-the-agents-in-on-the-box] ```bash prized agents handoff claude codex --gh ``` Claude Code and Codex come installed on the box; this signs them in there, and `--gh` copies your GitHub sign-in so Superset can clone private repos ([Credentials](https://prized.dev/docs/credentials)). For a box other than the one you set up, add `--box mango` after `prized`. Claude Code's sign-in is copied from this machine: with none here, it copies nothing for Claude, so sign in on the box instead (`prized ssh mango`, then `claude` and `/login`). ## 4. Add the box as a host [#4-add-the-box-as-a-host] If this box was forked or restored from a snapshot, give it its own machine id first, so Superset doesn't take it for the box it came from. On the box (`prized ssh mango`): ```bash title="you@box" sudo rm -f /etc/machine-id /var/lib/dbus/machine-id && sudo systemd-machine-id-setup ``` A box created before October 6, 2026 also needs `sudo loginctl enable-linger $USER` there, so the host keeps running after you log out. Then, on your computer, with the Superset CLI installed there: ```bash superset hosts add mango --name mango ``` Over `ssh mango`, it installs the Superset CLI on the box, signs it in, and runs it as a service that starts with the box. Remote access is part of Superset Pro ([Superset's docs](https://docs.superset.sh/remote-access)). `hosts add` needs Superset CLI 1.38.0 or later (`superset --version` shows yours). On an older CLI, start the host in a shell on the box instead (`prized ssh mango`); a host started this way stops when the box restarts: ```bash title="you@box" curl -fsSL https://superset.sh/cli/install.sh | sh ~/superset/bin/superset auth login ~/superset/bin/superset start --daemon ``` Then pick `mango` as the host for new workspaces in Superset; their worktrees live in `~/.superset/worktrees` on the box. ## 5. Pick a size [#5-pick-a-size] Agents waiting on the model need little; builds and tests need the room. As a starting point, count 2 vCPU and 8 GB per agent building at once: | Agents building at once | Size | | ----------------------- | ----------------------------- | | 2 | Small (4 vCPU, 16 GB) | | 4 | Medium (8 vCPU, 32 GB) | | 8 | Large (16 vCPU, 64 GB) | | 16 | Extra Large (32 vCPU, 128 GB) | ```bash prized box resize mango Large ``` Resizing restarts the box, which stops a host started by hand on an older Superset CLI: run `~/superset/bin/superset start --daemon` on the box again afterwards. The free credit runs a Small for about nine days around the clock; Medium and larger need a plan. An eight hour day on a Large is $5.48, and a sleeping box pays only for its disk; [Billing](https://prized.dev/docs/billing#sizes-and-rates) lists every size's hourly rate. ## When the box sleeps [#when-the-box-sleeps] On the free credit, a box pauses after 2 hours idle, never in its first 24 hours. An agent writing to a Superset terminal counts as use, but a host waiting for work does not, so the box pauses two hours after the last agent goes quiet and Superset shows the host offline ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)). To keep the host online, switch auto-pause off for this box, or set a longer window such as `12h`: ```bash prized box auto-pause mango off ``` Otherwise, `prized box resume mango --wait` wakes it, as does `prized ssh`. Save it as the host's wake command, and `superset hosts wake mango` runs it: ```bash superset hosts set-wake mango "prized box resume mango --wait" ``` A Medium or larger restarts on waking, so its terminals start fresh ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). If you started the Superset host by hand, run `~/superset/bin/superset start --daemon` on the box again after waking it. --- # Use opencode Desktop with a Prized box > Add a Prized box as an SSH server in opencode Desktop, so its sessions run on a cloud Linux machine that keeps working while your laptop sleeps. URL: https://prized.dev/docs/opencode-desktop [opencode](https://opencode.ai) Desktop can run its sessions on a Linux machine you reach over SSH. Add your box as that server, and the agent builds and tests on the box instead of your laptop. ## 1. Start a box [#1-start-a-box] ```bash npx prized ``` It signs you up, starts a box, and adds it to your SSH config ([Quickstart](https://prized.dev/docs/quickstart)). Use your box's name in place of `mango` (`prized box ls` lists them), and check that plain ssh reaches it: ```bash ssh mango echo ok # ok ``` ## 2. Keep the box awake [#2-keep-the-box-awake] An opencode server waiting for you counts as idle, so on the free credit the box pauses after 2 hours idle (never in its first 24 hours), and Desktop can't wake it. Switch auto-pause off for this box, or set a longer window such as `12h` ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)): ```bash prized box auto-pause mango off ``` ## 3. Add the box in opencode Desktop [#3-add-the-box-in-opencode-desktop] Open the command palette, choose **Add SSH server**, enter `mango` as the host, and connect. Desktop installs a matching opencode server on the box over your `ssh`, so there is nothing to install there first. Then open a project folder on the box, and connect a model provider for this server the first time, as you would locally. ## If the box paused [#if-the-box-paused] `prized box resume mango --wait` wakes it; then choose **Reconnect** in Desktop. A Medium or larger restarts on waking, so a turn that was running stops ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). --- # Run Happy Agent on a Prized box > Put Happy Agent on a Prized box as a remote server, so the Claude Code and Codex sessions you drive from Happy Desktop or your phone keep running while your laptop sleeps. URL: https://prized.dev/docs/happy [Happy](https://happy.engineering) can run its agent on any Linux server with systemd that you can SSH into with sudo ([Happy's guide](https://happy.engineering/guides/remote-server/)). A Prized box is one: Ubuntu 24.04, with passwordless `sudo`. ## 1. Start a box [#1-start-a-box] ```bash npx prized ``` It signs you up, starts a box, and adds it to your SSH config ([Quickstart](https://prized.dev/docs/quickstart)). Use your box's name in place of `mango` (`prized box ls` lists them), and check that plain ssh reaches it: ```bash ssh mango echo ok # ok ``` ## 2. Keep the box awake [#2-keep-the-box-awake] Happy Agent waiting for your phone counts as idle, so on the free credit the box pauses after 2 hours idle (never in its first 24 hours), and Happy can't wake it. Switch auto-pause off for this box, or set a longer window such as `7d` ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)): ```bash prized box auto-pause mango off ``` If it paused anyway, `prized box resume mango --wait` wakes it, and the Happy Agent service comes back with the box. ## 3. Ask Happy to install it [#3-ask-happy-to-install-it] In Happy Desktop, ask the Chief of Staff: ```text title="Prompt" Put Happy Agent on my server mango. Call it mango. ``` It installs Happy Agent over `ssh mango` as a system service and asks for your model sign-ins. Approve the AppArmor allowance it asks for: boxes keep Ubuntu 24.04's restriction on user namespaces ([Machines](https://prized.dev/docs/machines#operating-system)), and Happy's sandbox needs the grant to start. To drive it from your phone, ask the Chief of Staff to pair your phone ([Mobile Access](https://github.com/slopus/happy-agent/blob/main/docs/recipe/mobile-access.md)). --- # Run code from an AI agent in an isolated VM > Run an AI agent's commands in a microVM of its own: create a sandbox, run each command with a timeout, move files in and out, and delete it, with what it costs. URL: https://prized.dev/docs/sandboxes-run-agent-code Code an agent writes can delete files, fill a disk or loop forever, so run it away from the machine that runs the agent. A sandbox is a microVM made for that: * **Its own kernel.** A separate virtual machine, not a container sharing a kernel. * **No sudo.** Commands run as the user `prized`, so there is no `apt`; `pip` installs into a venv. * **Outbound only.** It reaches the internet, nothing reaches it from outside, and sandboxes cannot reach each other. * **Gone when you delete it,** disk and memory included. ## 1. Get an API key [#1-get-an-api-key] [Open Sandboxes](https://prized.dev/onboarding/sandboxes) and choose **Create API key**, or make one under **Dashboard → Settings → API keys** with **Full access**, or **Custom** with **Sandboxes: Write**. Then, where your agent runs, with `curl` and `jq` installed: ```bash export PRIZED_API_KEY=dcp_… API=https://api.prized.dev/api/v1 AUTH="Authorization: Bearer $PRIZED_API_KEY" ``` ## 2. Create a sandbox [#2-create-a-sandbox] ```bash SBX=$(curl -s -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" -d '{"template":"prized-base-v1","resources":{"vcpu":2,"memoryMb":4096},"maxRuntimeMin":60}' | jq -er '.sandbox.id // error(.error)') && while [ "$(curl -s "$API/sandboxes/$SBX" -H "$AUTH" | jq -r .sandbox.state)" = starting ]; do sleep 1; done && TOOLBOX=$(curl -s "$API/sandboxes/$SBX" -H "$AUTH" | jq -er .sandbox.toolboxBase) && curl -sS --retry 5 --retry-all-errors --fail -o /dev/null -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"true"}' ``` `prized-base-v1` has Python 3, Node.js 22 and git; `prized-agent-v1` adds Claude Code and Codex ([Templates](https://prized.dev/docs/sandboxes#templates)). `maxRuntimeMin` deletes the sandbox an hour after the create, so one your agent forgets stops costing money. The `&&`s stop at the first failure, so a refused create prints its error and sends nothing else. The last line runs `true` until the sandbox takes commands: for a moment after `running`, a request can still answer `409` or `502` ([The edge](https://prized.dev/docs/sandboxes-api#the-edge)). Retry only a harmless command like this one, since after a `502` the command may have run. ## 3. Run the agent's command [#3-run-the-agents-command] ```bash CMD='python3 -c "print(sum(range(10)))"' curl -s -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d "$(jq -n --arg c "$CMD" '{command: $c, timeout: 120}')" # {"stdout":"45\n","stderr":"","exitCode":0,"durationMs":24} ``` `jq --arg` passes the command through unchanged, quotes included; hand `stdout`, `stderr` and `exitCode` back to the agent as the tool's result. * **A failed command still answers `200`;** read `exitCode`. * **`timeout`** is in seconds, 300 unless set and 3,600 at most. At the limit, the command and the processes it started are killed (`"exitCode": -1, "timedOut": true`); one that left its process group, such as with `setsid`, runs on until you delete the sandbox. * **State carries over** from one command to the next. Commands run in `/home/prized/work` unless they name another `cwd`, and keep the first 4 MiB of each output stream. ## 4. Move files in and out [#4-move-files-in-and-out] Here the agent's script `main.py` goes in and runs, and the `out.csv` it writes comes back: ```bash curl -sS --fail -X POST "$TOOLBOX/files/upload?path=/home/prized/work/main.py" -H "$AUTH" --data-binary @main.py curl -s -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"python3 main.py"}' | jq .exitCode curl -sS --fail "$TOOLBOX/files/download?path=/home/prized/work/out.csv" -H "$AUTH" -o out.csv ``` Paths are absolute and inside `/home/prized`, up to 100 MiB a file ([Sandbox API: Files](https://prized.dev/docs/sandboxes-api#files)). With `curl --fail`, a failed copy says so and exits non-zero, rather than saving the error as `out.csv`; delete the sandbox only after the download worked. ## 5. Delete it [#5-delete-it] ```bash curl -sS --fail -X DELETE "$API/sandboxes/$SBX" -H "$AUTH" > /dev/null ``` > [!WARNING] > Deleting erases the sandbox's disk and memory, with no undo. Download what you need first. ## From the CLI [#from-the-cli] An agent with a shell can use the `prized` CLI (v0.21.0 or later), which reads `PRIZED_API_KEY` ([CLI](https://prized.dev/docs/cli#sandboxes)): ```bash ID=$(prized sandbox create --vcpu 2 --memory 4 --max-runtime 1h) prized sandbox cp main.py $ID:main.py prized sandbox exec $ID --timeout 2m -- python3 main.py prized sandbox cp $ID:out.csv out.csv && prized sandbox rm $ID ``` `exec` exits with the command's code, or 124 on a timeout. ## What it costs [#what-it-costs] A running sandbox bills per second, with no minimum: $0.04 per vCPU-hour, $0.0125 per GB-hour of memory, and $0.0001 per GB-hour of disk ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)). | | 2 vCPU, 4 GB | 4 vCPU, 8 GB (the default) | | ------------------------- | ------------ | -------------------------- | | Running, per hour | $0.131 | $0.261 | | One sandbox for 5 minutes | $0.011 | $0.022 | | 1,000 of them | $10.92 | $21.75 | Both include the 10 GB disk; paused, a sandbox bills only its disk and saved memory. ## On the free credit [#on-the-free-credit] The $30 of free credit covers about 2,700 five minute sandboxes at 2 vCPU. Until the workspace has paid in, it runs 4 sandboxes at once, 8 vCPU and 16 GB in total, each up to 2 vCPU, 4 GB and 60 minutes. A top-up or a plan lifts this to the full quotas and a lifetime of up to 24 hours, paused time included ([Free credit limits](https://prized.dev/docs/sandboxes#free-tier)). --- # Fork a running VM to run work in parallel > Set up one sandbox, fork it into live copies with the setup done, run a different job in each, collect the results, and delete them all, with what it costs. URL: https://prized.dev/docs/sandboxes-fork-parallel Cloning a repo and installing its dependencies is often the slow part of a job. Do it once in one sandbox, then fork it: each copy starts with the parent's disk, memory and running processes. A fork does not make one job faster; it runs different jobs at once from one setup, such as a test suite on three Node versions. The steps use the `prized` CLI (v0.21.0 or later), signed in or with `PRIZED_API_KEY` set ([CLI](https://prized.dev/docs/cli#sandboxes)). ## 1. Set it up once [#1-set-it-up-once] ```bash ID=$(prized sandbox create --vcpu 2 --memory 2 --max-runtime 1h) prized sandbox exec $ID --timeout 10m -- "git clone --depth 1 https://github.com/expressjs/express && cd express && npm install" prized sandbox exec $ID --timeout 10m -- 'for v in 20 22 24; do npm install --prefix ~/node$v node@$v; done' ``` The last command puts one Node version per copy in your home, since there is no sudo. Copies keep the parent's shape, and a small shape fits more of them. ## 2. Fork it [#2-fork-it] ```bash prized sandbox fork $ID --count 3 > copies.txt ``` This writes the copies' ids to `copies.txt` once each is running. The [free credit](#on-the-free-credit) allows a parent and 3 copies at once; a top-up or a plan allows more. Copies start on the parent's machine, so one fork makes at most: | Shape | Copies | | -------------------- | ------ | | 1 vCPU | 31 | | 2 vCPU | 15 | | 4 vCPU (the default) | 7 | | 8 vCPU | 3 | More memory or disk, or a busy machine, fits fewer, and every copy counts against the workspace's [quotas](https://prized.dev/docs/limits#sandboxes). A fork that does not fit makes nothing and answers `409 fork_capacity` with `fits`, how many fit now (the CLI prints it): fork that many, or set up a new sandbox and fork that ([Fork](https://prized.dev/docs/sandboxes#fork)). ## 3. Run a different job in each [#3-run-a-different-job-in-each] ```bash set -- 20 22 24 for C in $(cat copies.txt); do v=$1; shift (prized sandbox exec $C --cwd express -- "PATH=~/node$v/node_modules/.bin:\$PATH npm test" > node$v.log 2>&1; echo "node ${v}: exit $?") & done wait # node 22: exit 0 # node 20: exit 0 ``` `set --` lists the versions in the order of the copies, and each copy runs the whole suite on its own. To split one long suite instead, give each copy a shard, such as `npx vitest run --shard=2/3`. ## 4. Collect the results [#4-collect-the-results] Each job's output is in its log on your machine, `node20.log` and the others, and its exit code in the loop's output. A file a job writes stays in its copy until you copy it out. For a suite that writes a JUnit report to `report.xml`: ```bash prized sandbox cp $(head -1 copies.txt):express/report.xml report-node20.xml ``` ## 5. Delete the parent and the copies [#5-delete-the-parent-and-the-copies] ```bash prized sandbox rm $ID $(cat copies.txt) ``` Copies are ordinary sandboxes, billed like any other, so deleting the parent leaves them running. Each is deleted at its parent's deletion time at the latest, here an hour after the create. ## Over the API [#over-the-api] The fork is `POST /api/v1/sandboxes/{id}/fork` with `{"count": 3}` ([Sandbox API: Fork](https://prized.dev/docs/sandboxes-api#fork)). It answers with the copies, each `starting`; read each until `running`, then send a harmless `true` to its own `toolboxBase` until it answers `200`, and only then the real job. Retry only that `true`: after a `502` a command may have run ([The edge](https://prized.dev/docs/sandboxes-api#the-edge)). ## What it costs [#what-it-costs] Each copy bills per second at its own shape. A parent and 3 copies of 2 vCPU and 2 GB cost $0.424 an hour together, so 10 minutes of all four is $0.07 ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)). ## On the free credit [#on-the-free-credit] Until the workspace has paid in, it runs 4 sandboxes at once with 8 vCPU in total, each for up to 60 minutes: a parent and 3 copies of 2 vCPU. A fork past a limit makes nothing and answers `429 sandbox_quota_exceeded` with `admitted`, how many copies would have fit. A top-up or a plan lifts this to the full quotas ([Free credit limits](https://prized.dev/docs/sandboxes#free-tier)). --- # Moving from Runloop > Move Runloop devboxes to Prized sandboxes: the Prized command for each Runloop call, a short ported script, and what is not on Prized yet. URL: https://prized.dev/docs/moving-from-runloop Runloop shuts down on November 13, 2026 ([Runloop's notice](https://docs.runloop.ai/docs/transitions/overview)). ## 1. Export an API key [#1-export-an-api-key] A Runloop devbox becomes a Prized [sandbox](https://prized.dev/docs/sandboxes): a microVM that boots in seconds, pauses with its processes running, and forks into live copies. Make a key with **Sandboxes: Write** on the [API keys page](https://prized.dev/dashboard/workspace/api-keys), then export it: ```bash export PRIZED_API_KEY=dcp_… ``` ## 2. Swap each call [#2-swap-each-call] | Runloop | Prized | | ------------------------------------------------ | -------------------------------------------------------------------- | | `runloop.devbox.create()` | `prized sandbox create --vcpu 2 --memory 4 --max-runtime 1h` | | `devbox.cmd.exec(command)` | `prized sandbox exec ID -- CMD` | | `devbox.file.write()`, `file.read()` | `prized sandbox cp ./app.py ID:app.py`, and the reverse | | `devbox.suspend()`, `devbox.resume()` | `prized sandbox pause ID --wait`, `prized sandbox resume ID` | | `devbox.snapshot_disk()`, then `create_devbox()` | `prized sandbox fork ID --count 3`, live copies of a running sandbox | | `devbox.shutdown()` | `prized sandbox rm ID` | Each command has an HTTP route that takes the same key ([Sandbox API](https://prized.dev/docs/sandboxes-api)). ## 3. Port a script [#3-port-a-script] The same steps in Python with `requests`: ```python title="prized_job.py" import os, time, requests API = "https://api.prized.dev/api/v1" s = requests.Session() s.headers["Authorization"] = f"Bearer {os.environ['PRIZED_API_KEY']}" def run(box, command): return s.post(f"{box['toolboxBase']}/process/execute", json={"command": command}, timeout=600) def ready(sandbox_id): # running, and its first command answers while (box := s.get(f"{API}/sandboxes/{sandbox_id}").json()["sandbox"])["state"] == "starting": time.sleep(1) for _ in range(10): if run(box, "true").ok: return box time.sleep(1) box = ready(s.post(f"{API}/sandboxes", json={"template": "prized-base-v1", "resources": {"vcpu": 2, "memoryMb": 4096}, "maxRuntimeMin": 60}).json()["sandbox"]["id"]) ids = [box["id"]] try: print(run(box, "python3 --version").json()["stdout"]) s.post(f"{API}/sandboxes/{box['id']}/pause", params={"wait": "paused"}) s.post(f"{API}/sandboxes/{box['id']}/resume", params={"wait": "running"}) ids += [copy["id"] for copy in s.post(f"{API}/sandboxes/{box['id']}/fork", json={"count": 2}).json()["sandboxes"]] finally: for sandbox_id in reversed(ids): # copies first: a copy still starting fails without its parent s.delete(f"{API}/sandboxes/{sandbox_id}") ``` A failed command still answers `200`, so read `exitCode`. A fork answers `409 fork_capacity` when the parent's machine is full; start a new sandbox and fork that ([Fork](https://prized.dev/docs/sandboxes#fork)). ## Not on Prized yet [#not-on-prized-yet] * **Custom images and root.** Sandboxes start from two fixed templates without sudo, so Dockerfile blueprints, `apt`, and Docker need a [box](https://prized.dev/docs/boxes). * **Long lives and saved snapshots.** A sandbox lives 24 hours at most, and a fork copies a running sandbox, not a saved snapshot. * **Tunnels, SSH, named shells, network policies, Agent Gateways, and Axons.** ## Getting help moving [#getting-help-moving] Email the support address shown on https://prized.dev/docs/troubleshooting with what you run on Runloop, and we will help you map it. --- # Sync > Mirror a project folder between the box and your machine, both directions and .git included, with edits landing on the other side in under a second. URL: https://prized.dev/docs/sync ## The model [#the-model] | Side | Path | | ------------ | ---------------------------- | | box | `~/code/` | | your machine | `~/Prized/` by default | `prized sync add ` creates the pair; `--local` syncs a folder of your own in place, and `local_root` under `[sync]` in the config moves the default parent. The real local copy keeps whole-repo tools fast; to browse the box without copying, use [mount](https://prized.dev/docs/mount). ## How fast [#how-fast] Under a second, except the first edit in a folder nothing has touched for a while, which can take up to 10 seconds to leave the box; `watch_polling_interval` in the project's `[[sync.project]]` entry lowers that, at the cost of a full scan per interval. ## Add a project [#add-a-project] **Nothing exists yet.** `--create` makes `~/code/myproject` on the box: ```bash prized sync add myproject --create ``` **The code is on the box.** The first sync copies `~/code/myproject` down to `~/Prized/myproject` (`C:\Users\you\Prized\myproject` on Windows). For a folder outside `~/code`, pass its path (`prized sync add ~/work/api` names it `api`); `--name` picks the name yourself: ```bash prized sync add myproject ``` **The code is on your machine.** Run setup in the folder, or point it there. Auto mode takes a folder holding a `.git` directory or a manifest (`package.json`, `go.mod`, `pyproject.toml`, and the like) without asking, and asks about any other, so your home folder never syncs by accident. A manual run also asks whether to sync in place (the default) or copy once; two more modes are flags: ```bash prized setup ~/src/myproject # asks: sync in place? (n = copy once) prized setup ~/src/myproject --copy-once # copy to ~/code/myproject on the box, no sync prized setup ~/src/myproject --copy # sync a copy under ~/Prized prized setup ~/src/myproject --clone # git clone on the box, no mirror ``` | Mode | What happens | Right for | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | | **In place** | Your folder becomes the local side; setup asks first, since the box wins conflicts there. | A project one person edits. | | `--copy-once` | One upload to `~/code/myproject`, `.git` included, ignored paths and `.env` files left out; no sync, and a populated destination is never overwritten. | A checkout others or several agents edit, or a one-time import. | | `--copy` | Copies (never moves) the folder into `~/Prized/myproject` and syncs the copy; a hard link inside is refused. | Leaving the original untouched. | | `--clone` | Clones the folder's `origin` to `~/code/myproject` on the box at your current branch; no sync, work moves by push and pull. | A checkout others edit, worktrees, uncommitted work in flight. | In place by hand is `prized sync add --local ~/src/myproject --create`. A clone is not a sync project, so it never appears in `prized sync ls`. | Rule for every add | What happens | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Both sides have content | `add` refuses until you pass `--merge` (`prized setup --merge` passes it through), which accepts the conflict rule below; the local folder is first copied to `~/Prized/.prized/backups/`. | | No nesting | A folder cannot be added inside, around, or on top of another synced folder, on either side. | | Ignored by default | `node_modules`, `.next`, `dist`, `build`, `out`, `target`, `.venv`, `venv`, `__pycache__`, `.turbo`, `coverage`, `*.log`, `.DS_Store`, `._*`, `.tox`, `.nox`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`, `.ipynb_checkpoints`, `.pnpm-store`, `.parcel-cache`, `.nuxt`, `.svelte-kit`, `.cache`, `.gradle`, `.terraform`. Adjust per project with `--ignore` and `--unignore`. | | `.env` files | A session `prized setup` creates ignores `.env` and `.env.*` (`.env.example` still syncs); `prized sync add` by hand syncs them unless you pass `--ignore .env` ([Bring your environment](https://prized.dev/docs/environment#env-files)). | | Files over 256 MB | Skipped; `sync add` and `prized sync ls` name every skipped path. | If the first sync cannot be set up, `sync add` fails with the reason but keeps the project, shown as `error` in `prized sync ls` while the daemon retries. `prized daemon restart`, or a re-run of `prized setup`, tries again at once; `prized sync rm myproject` drops it. Moving the whole folder, `.git` included, can take a couple of minutes: watch the transfer rate on setup's progress line before assuming it is stuck, trim with `--ignore`, or use `--clone` so the box fetches the history from `origin`. ## Low disk on your machine [#low-disk-on-your-machine] A project larger than your free disk never finishes its first sync. When free space on any volume sync writes to drops below 2 GB, Prized pauses every synced project and `prized sync ls` says so: ```text title="prized sync ls" NAME STATE LAST CHANGE CONFLICTS LOCAL REMOTE doppel paused - 0 ~/Prized/doppel ~/code/doppel doppel: paused by prizedd — low disk: 1.5 GB free on ~/Prized/doppel, below min_free_disk_gb = 2 ``` Free space, or add `extra_ignores` for folders that should not travel (render output, datasets, media), then `prized sync resume --all`. While a volume is below the line, a resume is refused and a newly added project starts paused; `prized sync add` onto a volume that is itself below it is refused. The threshold is `min_free_disk_gb` under `[sync]` in the config (`0` turns the guard off), applied on the next reload. ```toml [[sync.project]] name = 'doppel' extra_ignores = ['artifacts'] ``` ## Conflicts: the box wins [#conflicts-the-box-wins] Sync runs two-way-resolved and the box wins every conflict, since agents there write fast; in a folder synced in place, your own checkout is the side that yields. `--prefer mac` flips that per project; `--mode two-way-safe` surfaces conflicts instead of resolving them. ## Git over sync [#git-over-sync] Commit, rebase, and push from a shell on the box. Reading history, diffs, and blame in `~/Prized` is always safe; committing from your machine is safe only while the box side is quiet, or the local commit can be lost. ## Offline [#offline] Agents, builds, and servers keep running while your laptop is offline; sync and mirrored ports catch up on reconnect. Before editing offline where an agent also writes, pause the project, or the box's versions win on reconnect. Behind a proxy, sync uses your Mac's system proxy, or your shell's `HTTPS_PROXY` once you run `prized setup` there ([Networks that block the tunnel](https://prized.dev/docs/troubleshooting#networks-that-block-the-tunnel)). ## Everyday commands [#everyday-commands] | Command | What it does | | ------------------------------ | -------------------------------------------------------------------------------------------------- | | `prized sync` | List projects with state and conflicts (same as `prized sync ls`); says so when nothing is synced. | | `prized sync flush myproject` | Force a full sync cycle and wait for it, for example before switching machines. | | `prized sync pause myproject` | Stop syncing until you resume; `--all` works too. | | `prized sync resume myproject` | Resume a paused project. | | `prized sync repair myproject` | Recover a halted session with a safe re-scan; no deletions propagate. | > A session halted because its root folder was deleted refuses to resume, which would propagate the mass delete; use `repair`. ## Remove a project [#remove-a-project] ```bash prized sync rm myproject ``` This stops syncing and leaves the files on both sides. Two flags also move files aside (never `rm -rf`), naming the exact path in the confirm: * **`--delete-local`** moves the local folder to the Trash after you type `delete-local`; for a project synced in place, that is your own checkout. * **`--delete-box`** moves the box folder aside after you type `delete-box`; refused while anything else syncs that folder or one inside it. > Box paths that spell one folder differently, such as `~/code/app` and `/home/ubuntu/code/app`, count as the same folder. ## Several boxes [#several-boxes] Aim sync commands at a box connected with `prized setup --box mango` by adding `--box mango` (or `PRIZED_BOX=mango`); two boxes cannot sync a project under one name, so the second `prized sync add` asks for `--name` ([Boxes](https://prized.dev/docs/boxes#several-boxes)). ## Under the hood [#under-the-hood] Sync runs on Mutagen: Prized uses its own pinned copy, never one on your PATH, and `prized doctor` fetches it if it is missing. --- # Mount > Browse the whole box in Finder as a live network drive; nothing is copied and it uses no disk space on your Mac. URL: https://prized.dev/docs/mount > macOS only. On Linux and Windows the command refuses and points at [sync](https://prized.dev/docs/sync) and `prized ssh` instead. ## Usage [#usage] ```bash prized mount # the configured box, in the background prized mount mango # any box on your account prized mount --dir /var/log --read-only # one directory, read-only prized unmount # stop the mount and detach the volume ``` `prized mount` wakes the box, mounts its home directory at `~/Prized/boxes/` (in Finder under Locations as `.localhost`), opens it in Finder, and returns; the mount outlives the terminal. Run it again for a mounted box and it opens the folder. `prized unmount [BOX]`, or ejecting the volume, stops it. | Flag | What it does | | -------------------- | ------------------------------------------------------------ | | `--dir` | Export this box directory instead of the home directory. | | `--at` | Mount somewhere other than `~/Prized/boxes/`. | | `--read-only` | Serve a read-only view. | | `--no-open` | Do not open the folder in Finder. | | `--foreground`, `-f` | Serve from this terminal until Ctrl-C. | | `--mount-opt` | Extra NFS mount options, comma-separated, appended verbatim. | One mount per box. A background mount logs to `~/Library/Logs/prized/mount-.log`; `prized mount` and `prized unmount` take `--json`. ## When the box sleeps or the connection drops [#when-the-box-sleeps-or-the-connection-drops] The volume survives a pause, your Mac sleeping, or a dropped connection: the next access waits for the box to wake and reconnect (Finder may spin for a few seconds), and a copy in progress resumes rather than failing. Ten minutes without a connection unmounts it; `prized mount` brings it back. ## Mount or sync? [#mount-or-sync] | You want | Use | | ----------------------------------------------------- | ------------------ | | a working copy on your Mac, fast for builds and git | [sync](https://prized.dev/docs/sync) | | to browse, preview, edit, or grab anything on the box | mount | Each uncached operation on a mount is a network round trip: browsing, Quick Look, editing a file, and copying either way feel fine; builds or `git status` on a large repo do not. ## How it works [#how-it-works] The stock macOS NFS client talks to a server the CLI runs on 127.0.0.1, which reaches the box over SFTP through the `prized ssh` tunnel. Nothing kernel-side is installed, no sudo is needed, and your Mac holds file bytes only in memory. > [!WARNING] > That local server does not authenticate its clients: on a Mac shared with other user accounts, treat a mount as readable by them. Export a narrower `--dir`, add `--read-only`, and `prized unmount` when you are done. ## Troubleshooting [#troubleshooting] | Symptom | Fix | | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Startup times out | The box stopped answering before the volume was ready: `prized doctor`, then retry. With `--foreground`, Ctrl-C also cancels startup. | | "already mounted at ... read-only, not writable" (or a different `--dir` or `--at`) | The mounted volume does not match your options: `prized unmount `, then mount again. | | "mountpoint is not empty" | A mount left by a previous run: `umount ~/Prized/boxes/` and retry. | | "Operation not permitted" listing the folder in a terminal | Give your terminal Full Disk Access (System Settings > Privacy and Security), or use Finder. | | "secure sftp helper failed (update prized-agent on the box)" | The box's agent predates mount; update it and retry. | | The folder hangs on a box that no longer exists | A deleted or stopped box cannot be woken; `prized unmount ` detaches the volume. | --- # Ports > A server on the box opens on your machine as localhost within about two seconds, with no flags or config, and any port can be shared at a link, with your workspace or with anyone you send it to. URL: https://prized.dev/docs/ports ## How it works [#how-it-works] A TCP port opens at the same number on your machine's 127.0.0.1, websockets included; servers bound only to the box's loopback (Vite, most dev servers) work too. On Windows this works while you are signed in. ```bash title="you@box ~/code/myproject" npm run dev # ready on http://localhost:3000 # two seconds later, localhost:3000 works on your machine ``` ## Inspect and toggle [#inspect-and-toggle] | Command | What it does | | ---------------------------- | --------------------------------------------------------------- | | `prized ports ls` | Every box listener, its process, and its mirror state. | | `prized ports toggle 3000` | Flip a port off or back on; the choice survives restarts. | | `prized proxy box-3fa9 5432` | Forward one port from any box to `127.0.0.1:5432` until Ctrl-C. | An interactive `prized ssh` or `prized mosh` session to a non-default box forwards that box's ports while it lasts. ## Limits [#limits] | Limit | What it means | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | TCP only | UDP is not forwarded. | | Ports 3000 through 9999 | Auto-mirrored; the range and per-port allow and never lists are in the config's `[ports]` section. | | Never mirrored | Ports below 1024, port 22, and the agent's port 7377; 5900 is off by default. Other ports outside the range need an allow-list entry or `prized proxy`. | | Open to your whole machine | Like `ssh -L` or `docker -p 127.0.0.1:`: on a machine shared with other accounts, treat a mirrored port as unprotected. | | Local wins | If something on your machine already uses the port, the mirror waits, `prized proxy` refuses it, and an `ssh` session skips it. | | Every request crosses the network | Run an app that makes many database round trips per page on the box, beside its database, and mirror only the app port. | ## Share a port [#share-a-port] `prized ports share 3000` gives the port a link your workspace's members open in a browser, with nothing to install. Two wider audiences need a paying workspace: `--anyone` (whoever holds the link) and `--public` (anyone at all, owner only). ```bash prized ports share 3000 # ✓ port 3000 on mango shared # https://mango-3000-k7x2q9.p.prized.dev/ ``` A link anyone can open for three days, for a client who has no Prized account: ```bash prized ports share 3000 --anyone --expires 3d ``` | Audience | Who opens it | Link | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | Members (the default) | Members of your workspace who can see the box, after signing in to Prized; anyone else sees a page saying the link is not available to their account. | Under `p.prized.dev`; lasts until you stop sharing. | | Anyone with the link | Whoever holds the link, with no Prized account. The link carries a secret and is shown once, when you make it; lost, make a new one. | On a separate domain, never prized.dev; expires after 7 days by default, 5 minutes to 30 days. | | Public | Anyone at all. | On the same separate domain; same expiry. | The first visit to an anyone or public link from a browser shows a warning page: Prized did not make the page, so visitors should not type passwords into it unless they trust the sender. **Continue** opens the app; the browser is not asked again while its visit lasts. A port has one link: sharing it with another audience (`--anyone`, `--public`, or a different choice on the dashboard) replaces the link, and the old one stops working at once; `prized ports share 3000` with no flag on a shared port prints the link it has. The same choice is on the box page's Overview: click a port in the Machine tile's **Ports** row ([Dashboard](https://prized.dev/docs/dashboard#overview)); `prized ports share` with no port lists what the box shares. To share a port nothing listens on yet, click **+** at the end of the **Ports** row and type its number; nothing is shared until you click the button that names who can open it (**Make public for 7 days**, for example). | Topic | What happens | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | What it carries | HTTP and websockets, so hot reload works. The app sees itself reached as `localhost:3000`, so host and origin checks pass. Anything else (a database, SSH) needs [`prized proxy`](#inspect-and-toggle). | | A paused box | A members link wakes it, and the page retries until the box answers. An anyone or public link never wakes it: visitors see a page saying the box is asleep until someone in the workspace starts it. | | Who may make one | Any member makes a members link; a [contractor](https://prized.dev/docs/teams#roles) only on boxes they own. An anyone link: the owner and members, or the owner alone under the `anyoneLinksOwnerOnly` [policy](https://prized.dev/docs/teams#policies). A public link: the owner. Contractors make neither. | | Paying workspaces | Anyone and public links need a paid plan or a purchase of credit; the free credit makes members links only. A workspace holds up to 10 live anyone and public links at once ([Limits](https://prized.dev/docs/limits#sync-sessions-and-ports)). | | Stop sharing | `prized ports unshare 3000`, or **Stop sharing** on the dashboard, ends the link within a moment, open pages and websockets included; so does deleting the box or closing the workspace. Removing a member, or making them a contractor, ends their members access and the anyone and public links they made. | | Cookies | Each link has its own host. A cookie your app sets with a wider `Domain` is stored for that link alone, and your Prized sign-in never reaches the app. | | "Too many requests" | A browser or script is asking faster than a shared port serves, or a link has had many visitors in a minute; reload in a moment. | | The log | Sharing, stopping, each member's first opening, and every visitor's first opening of an anyone or public link are rows on the [audit log](https://prized.dev/docs/teams#what-is-recorded). | Links pass through the browser terminal's relay, which can see the traffic as it passes and stores nothing ([Security](https://prized.dev/docs/security#other-ways-in)). ## A second box [#a-second-box] A box connected with `prized setup --box mango` mirrors its own ports; aim commands at it with `prized --box mango ports ls` and `prized --box mango ports toggle 3000` ([Several boxes](https://prized.dev/docs/boxes#several-boxes)). The same port on two boxes collides on your machine: the second mirror shows as `conflicted` in `ports ls`; toggle it off on one box, or add it to `[ports].never` in that box's config. --- # Sessions > Your shell runs in a persistent session on the box, so a network drop or a closed lid ends only the connection; reconnect and you are where you left off, running processes included. URL: https://prized.dev/docs/sessions ## Connect [#connect] ```bash prized ssh ``` You land in a tmux session named `main`, the same one from anywhere, and a suspended box wakes first. On Windows it uses the built-in OpenSSH client; mosh and dragging files in are [not on Windows yet](https://prized.dev/docs/cli#windows). | Command | What it does | | -------------------------- | ------------------------------------------------------------------------------------------ | | `prized ssh -s agents` | Attach to, or create, a named session: letters, digits, hyphens, and underscores; no dots. | | `prized ssh --ls` | List sessions. | | `prized ssh --kill agents` | Close a session. | | `prized ssh --raw` | A plain shell without tmux. | | `prized ssh -- make test` | Run one command and get its exit code back. | | `prized agents run codex` | Start a coding agent in a detached session ([Agents](https://prized.dev/docs/agents)). | > What a session survives on a pause depends on the box size ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)); a busy session holds off [auto-pause](https://prized.dev/docs/boxes#auto-pause). ## When the connection drops [#when-the-connection-drops] On macOS and Linux, if the network goes away mid-session (Wi-Fi changes, the lid closes), `prized ssh` notices within about a minute, prints one line, and reconnects on its own once the box answers again, back in the same session. A box paused in the meantime (from the dashboard, `prized pause`, or auto-pause) stays paused and the session ends; connecting again wakes it. Press Ctrl+C to stop waiting. ssh's own `~.` still ends the session, and `--raw` shells and one-off commands end instead of reconnecting. Anything you type while waiting to reconnect is discarded, not sent to the box. Wait for the session to return before typing. Your terminal is put back the way it was when the connection dies, so tmux or Claude Code going away mid-screen no longer leaves mouse movements and keys printing as codes in your shell. Prefer a multiplexer that shows what each agent is doing? `prized herdr` lands in [herdr](https://prized.dev/docs/herdr) instead of tmux. ## Mosh for flaky networks [#mosh-for-flaky-networks] ```bash brew install mosh # macOS; on Linux: sudo apt install mosh prized mosh ``` The same tmux session over UDP: keystrokes echo instantly, and the connection survives IP changes and laptop sleep. It runs through the SSH tunnel, so the box needs no public port. ## Drag files in [#drag-files-in] Drag files or folders from Finder (or your file manager) onto a `prized ssh` or `prized mosh` window: they upload to a fresh folder under `~/.prized/drops/`, and the paste becomes their box paths, so Claude Code or Codex can read them. | Rule | What happens | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Limits | Up to 16 files or folders per drop, 512 MiB and 4,096 files in total. Folder scans stop at 8,192 entries or 64 levels deep; transfers time out after 5 minutes. Drops older than 7 days are cleaned out. | | Skipped | In folders: `.env` and `.env.*` (`.env.example` and `.env.sample` still upload), `.git`, dependency, build, and cache folders such as `node_modules`, `.venv`, and `target`, and your `[sync].default_ignores` ([the patterns sync leaves behind](https://prized.dev/docs/sync#add-a-project)), read with the dropped folder as the project root. Symlinks, hard-linked files, and special files inside folders stay local. The status line counts files and skips. | | Names | Spaces and shell punctuation become underscores, inside folders too; leading dots and dashes are removed; collisions get numbered suffixes. | | Fail-safe | Only a bracketed paste of nothing but existing local paths is rewritten. Empty or fully skipped folders, over-limit drops, and failed uploads paste the original paths, and the status line says why; a symlink or hard-linked file dropped directly is not uploaded. | | Terminals | Ghostty, Terminal.app, and iTerm2, once the session enables bracketed paste. Not plain SSH, editor Remote-SSH, explicit remote commands, or Windows. | | Off | For one session with `--no-drop-upload`, or for good with `drop_upload = false` under `[ssh]` in the config. | ## Copy out [#copy-out] Text copied in the session lands on your Mac or Linux clipboard, in Apple Terminal too: select it in Claude Code and press **c**, or use tmux copy mode. On a fresh box that is usually Claude's sign-in link: press **c**, paste it in a browser, paste the code back. | Rule | What happens | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | How | `prized ssh` writes the copy to your clipboard with pbcopy on macOS, or wl-copy, xclip, or xsel on Linux. | | One way only | Nothing on the box can read your clipboard; read requests are dropped. Anything on the box can copy into it, so glance at what you paste into a shell. | | Selecting with the mouse | Claude Code takes over the mouse: hold **Fn** (Apple Terminal), **Option** (iTerm2), or **Shift** (most others) while dragging to select in the terminal, then Cmd-C. | | Not over mosh | `prized mosh` does not carry the copy. `prized ssh --raw` does; on Windows the terminal handles the copy itself. | | Off | For one session with `--no-clipboard`, or for good with `clipboard = false` under `[ssh]` in the config; copies then stay on the box. | ## Editors and other tools [#editors-and-other-tools] * **Your local editor on the synced folder.** Saves to `~/Prized/myproject` land on the box in under a second. * **Anything that speaks SSH.** `prized setup` installs an SSH config stanza, so `ssh box-3fa9`, Cursor and VS Code Remote-SSH, scp, rsync, and git remotes reach the box by hostname. The ChatGPT desktop app uses it too ([Codex remote](https://prized.dev/docs/codex)), and Claude Desktop with a restricted device key Prized installs ([Claude Desktop remote](https://prized.dev/docs/claude-desktop)). None of them wakes a paused box: run `prized up` first, then reconnect. --- # herdr > Land in herdr instead of tmux, a terminal multiplexer that shows which of your coding agents is working, waiting on you, or done. URL: https://prized.dev/docs/herdr ## Connect [#connect] ```bash prized herdr ``` The first connect installs [herdr](https://github.com/herdrdev/herdr), an open-source terminal multiplexer built for coding agents, into `~/.local/bin` on the box: a checksum-verified release of about 30 MB, with a progress bar, usually done in a second or two. Later connects reuse it, and a herdr you installed yourself is used as is. You land in herdr's default session, the same one `herdr` opens from any shell on the box, and a suspended box wakes first. The first time, herdr shows a one-screen welcome; press Enter. Its settings then open on agent integrations: installing the ones for Claude Code and Codex makes their states exact. Press Esc to skip. When you detach, `prized herdr` confirms that herdr and your agents are still running. | Command | What it does | | ------------------------------- | ----------------------------------------------------------- | | `prized herdr my-box` | Connect to another box. | | `prized herdr -s agents` | Attach to, or create, a named herdr session. | | `prized herdr --no-forward` | Leave the box's ports alone ([Ports](https://prized.dev/docs/ports)). | | `prized herdr --no-drop-upload` | Paste dragged files as plain paths, without uploading them. | | `prized herdr --no-clipboard` | Keep copies made on the box off this machine's clipboard. | ## Find the agent that needs you [#find-the-agent-that-needs-you] The sidebar lists every agent with its state: working, blocked (waiting on you), idle, or done. Press `ctrl+b g` to list them all, then `b` for the blocked ones and Enter to jump there. Start an agent in a new tab with `ctrl+b c`, then `claude` or `codex`. When an agent finishes, or is blocked waiting on you, you get a notification on your computer, like any app's. herdr skips it while you're looking at that agent. * **Ghostty, iTerm2, WezTerm and kitty** show it themselves. `prized herdr` tells herdr which terminal you're in, since ssh doesn't. Your terminal may hold it back while its window is in front. * **Any other terminal** (Terminal.app, VS Code's, or one inside a local tmux) gets it from prized, through macOS's notifications or `notify-send` on Linux. Without either, the terminal's bell rings. On a Mac, if no notifications appear, allow them for your terminal, or for Script Editor (which posts prized's), in System Settings › Notifications. Drag-and-drop progress arrives the same way. | Keys | Action | | ----------------------- | -------------------------------- | | `ctrl+b c` | New tab | | `ctrl+b v` / `ctrl+b -` | Split right / down | | `ctrl+b h` `j` `k` `l` | Move between panes | | `ctrl+b g` | Jump to any agent | | `ctrl+b q` | Detach; everything keeps running | | `ctrl+b s` / `ctrl+b ?` | Settings / every key | The mouse works too: click tabs and agents, drag pane borders, right-click for menus. ## Works like `prized ssh` [#works-like-prized-ssh] On macOS and Linux: * **Drag files in.** Files dropped on the window upload to `~/.prized/drops/` and paste as box paths; the progress arrives as a notification ([Sessions](https://prized.dev/docs/sessions#drag-files-in)). * **Copy out.** A copy made on the box, Claude Code's included, lands on this machine's clipboard. * **Dropped connections reconnect** to the same herdr session ([Sessions](https://prized.dev/docs/sessions#when-the-connection-drops)); a box paused meanwhile stays paused. ## From the dashboard and your phone [#from-the-dashboard-and-your-phone] Once a box has herdr, its **Terminal** tab shows **tmux** and **herdr** at the top right. Pick **herdr** to land in herdr's default session, the one `prized herdr` opens, so the agents you run there are a tap away on your phone. * **Narrow screens:** herdr folds its sidebar into a bar at the top. * **Toasts:** when an agent finishes or needs you, a toast shows over the terminal. Very large notification messages are ignored to keep the terminal responsive. * **Taps and drags:** taps reach herdr as clicks without raising the keyboard, and a drag scrolls the agent in front of you. * **Typing:** tap Claude Code's prompt, or the keyboard key under the terminal; **ctrl** there, then b and a key, gives herdr's `ctrl+b` keys (ctrl, b, c opens a new tab). * **The choice sticks:** the browser remembers it for each box, and `?mux=herdr` in the tab's address picks it directly. Your laptop and phone can show the same herdr at once. Each screen's view fits the tab it is on; when both are on the same tab, the screen you last tapped, clicked, or typed in sets that tab's size. After you use the phone, the laptop shows phone-sized panes in that tab until you click back into it. > herdr and tmux are separate: sessions started in one do not show in the other, and `prized ssh` and `prized mosh` open tmux. The first install also writes herdr's config. Notifications go to your computer, as above. New panes start login shells, as tmux's do, so `~/.profile` runs and `claude` and `codex` are on the `PATH`. A box set up by an earlier `prized herdr` switches to these notifications the next time you connect, unless you've changed its config. Change it with `ctrl+b s`. > If your shell startup starts tmux whenever `$TMUX` is empty, every herdr pane opens tmux. Skip it inside herdr by adding `[ -z "$HERDR_ENV" ]` to that check. --- # Run commands and files > Run a command on your box and get its exit code, leave one running and follow it live, read any command's output later even with the box paused, or copy files either way, from the CLI or any script holding an API key, with nothing to install on the box. URL: https://prized.dev/docs/exec ## Run a command [#run-a-command] ```bash prized exec -- make test ``` It runs under `sh -c` in your home directory on the box with your local stdin connected, and stdout, stderr, and the exit code come straight back. A suspended box wakes first; a box name before `--` picks another box. | Command | What it does | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prized exec -- npm test` | Run in your home on the default box; the exit code is `npm test`'s. | | `prized exec box-a7f3 --cwd app -- npm test` | Run on a named box, in `~/app` (`--cwd` is relative to your home, or absolute). | | `prized exec --timeout 10m -- ./build.sh` | Kill the command after 10 minutes (TERM, then KILL 5 s later): exit 124, and stderr ends with `prized: command timed out after 600s (SIGTERM)`. No limit by default. | | `prized exec --env CI=1 --env NODE_ENV=test -- npm test` | Add environment variables (repeatable, up to 32). | | `prized exec --json -- make` | Capture instead of stream: one object with `exitCode`, `signal`, `timedOut`, `stdout`, `stderr`. | * **Quoting.** The words after `--` are joined and passed to `sh -c`, so quote as for ssh: `prized exec -- 'cd app && npm test'` runs both halves on the box. It is a plain `sh`, not your login shell; tools only your `.bashrc` puts on PATH need `prized exec -- bash -lc 'npm test'`. * **`--timeout`** here limits the command, not the network request as on other commands. * **Exit code:** the remote command's, or ssh's 255 when the box was unreachable, after a `prized:` line saying why ([Troubleshooting](https://prized.dev/docs/troubleshooting#networks-that-block-the-tunnel)). With `--json`, `ok` says whether the command ran and `exitCode` what it returned; an unreachable box exits 3 with the reason in `error`. `timedOut` is true only when the time limit ended the command (one exiting 124 by itself is `exitCode: 124, timedOut: false`). ## Detached processes [#detached-processes] ```bash prized exec --detach -- ./train.sh # started p_3f9c0a1b2d4e (pid 41213) in /home/ubuntu ``` `--detach` returns a process id at once; the process outlives your terminal and ssh session, logging to files on the box. | Command | What it does | | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- | | `prized exec --detach -- CMD` | Start `CMD` detached (`--cwd` and `--env` apply) and print its id. | | `prized exec --status p_3f9c0a1b2d4e` | Running or exited, the exit code or signal, start and finish times, and the tail of each log. | | `prized exec --status p_… --tail 1m` | How much of each log to show: bytes, or `4k`, `1m` (default `16k`, cap `1m`). | | `prized exec --kill p_3f9c0a1b2d4e` | Stop it: TERM to its process group, then KILL if it is still there after 5 s. | | `prized exec --ps` | Every detached process on the box, running or finished. | | `prized exec logs p_3f9c0a1b2d4e --follow` | Print its output live until it ends, then exit with its exit code ([Command history](#command-history)). | `--status`, `--kill`, and `--ps` never wake a suspended box (they exit 5 and say so); starting a command does. `logs --follow` never wakes one either: it prints what the history keeps and exits 0 while the command is still running. The process id is also the command's id in its [history](#command-history). Each detached process keeps everything in one directory on the box, for `ls`, `tail -f`, and your scripts: | File in `~/.prized/processes//` | Contents | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cmd`, `cwd` | The command line as given, and the absolute directory it ran in. | | `pid` | The process id (also its process group id). | | `boot_id` | The box's boot id at start. After a reboot the process reads as `lost` and is never signalled; Medium and larger boxes reboot on resume, any box after a resize, a region move, or a wake from deep sleep ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). | | `started_at` | Start time, RFC 3339 UTC. | | `stdout.log`, `stderr.log` | The output, appended as it happens. | | `exit` | Written when the command ends: the exit code, or 128 + signal. | > Log directories are never deleted for you: `rm -r ~/.prized/processes/p_…` on the box when you are done with one. ## Command history [#command-history] ```bash prized exec history # ID STATUS MODE STARTED COMMAND # p_3f9c0a1b2d4e exited 0 detached 2026-10-11 14:02:11 ./train.sh # p_81c07e9d55a0 exited 1 sync 2026-10-11 13:58:40 npm test ``` Every command `prized exec` or `POST /exec` runs, in the foreground or detached, is recorded: the command line, the directory, who ran it, how and when it ended, how many bytes it printed, and up to the last 1 MiB of its stdout and of its stderr. History lives with Prized, not on the box, so it reads the same while the box is paused or stopped. Each record is kept 30 days. | Command | What it does | | ---------------------------------- | ---------------------------------------------------------------------------------------------------- | | `prized exec history [BOX]` | The box's commands, newest first; `--limit N` and `--before CURSOR` page through older ones. | | `prized exec logs [BOX] ID` | One command's stdout, as much as is kept; `--stderr` for stderr, `--offset N` to start at a byte. | | `prized exec logs ID --follow` | A running detached command's output live, from the box, until it ends; then the command's exit code. | | `prized exec history --delete ID` | Delete one record and its output. | | `prized exec history --delete-all` | Delete every record of the box (asks unless `-y`). | * **Terminal display:** logs remove control characters except tabs and newlines, so colors and cursor movements are not replayed. Files and pipes receive the original bytes; view those exports with a terminal-safe viewer. `--offset` counts original bytes, not displayed characters. * **Status** is `running`, `exited`, `killed` (ended by a signal), `timed_out`, `failed` (it never ran: the box was unreachable, or a detached start failed), or `lost` (the box restarted under a detached command, or it reported nothing for 24 hours). * **Detached output** reaches the history from the box every few seconds; the last few seconds before a pause show up once the box runs again. `--follow` reads the box itself, so it is live and complete. * **Untrusted live address:** if `--follow` refuses the live-log address, it says so and prints retained history instead, without sending your login token there. Output may be incomplete while the command is still running. * **Past 1 MiB** a stream keeps its end and is marked truncated (`stdoutTruncated`). A stream also keeps at most 256 stored chunks: adjacent small writes are combined, but gaps can make this limit trim older output sooner. Output pages report missing bytes as `skipped`. The log in `~/.prized/processes//` on the box keeps everything. * **Who can read it:** anyone in the workspace who can see the box, so a contractor only their own boxes, and a [key limited to some boxes](https://prized.dev/docs/workspaces#limit-a-key-to-some-boxes) only those boxes (any other answers `404`). A limited API key needs Boxes: Write to read history, since command lines and output can carry secrets. * **Deleting the box** deletes its history. > Output can carry a secret a command printed. Delete that record with `--delete`, or turn on [zero data retention](https://prized.dev/docs/data-retention#zero-data-retention), under which history keeps no command line, directory, or output. ## Copy files [#copy-files] ```bash prized cp ./notes.md :notes.md ``` `prized cp` works like scp: exactly one side is on the box, written `BOX:PATH` (a bare `:PATH` is the configured box), and box paths are relative to your home unless absolute. Files are written whole and keep their modes, so an interrupted copy never leaves a half-written file; if one file fails, the copy stops with its error and the files already copied stay. * **Symlinks** are skipped and counted, one named as `SRC` included: name the file it points at instead. * **Hard links** (common in pnpm and Nix trees) are skipped and counted the same way, and refused as `SRC`: copy the file to a new name to send it. * **A download writes only under `DST`:** an entry that would land outside it stops the copy, and so does a target directory that is itself a symlink (name the directory it points at). | Command | What it does | | ------------------------------------ | --------------------------------------------------------- | | `prized cp ./file.txt :` | Copy a file into your home on the box, keeping its name. | | `prized cp -r ./site box-a7f3:www` | Copy a directory tree (`-r` is required for directories). | | `prized cp box-a7f3:logs/app.log ./` | Copy a file from the box into the current directory. | | `prized cp -r :proj/dist ./dist` | Copy a directory tree from the box. | For a live view of the whole box in Finder instead, see [Mount](https://prized.dev/docs/mount). ## The edge API [#the-edge-api] Everything above is also HTTP, for scripts and agents with no `prized` installed: routes sit under `$EDGE/v1/box/{box}/` (the box's name or id), the bearer is an API key from **Dashboard → Settings → API keys**, and the base URL is `edge.url` in `GET /api/v1/me`. ```bash export PRIZED_API_KEY=dcp_… EDGE=$(curl -s -H "Authorization: Bearer $PRIZED_API_KEY" https://api.prized.dev/api/v1/me | jq -r .edge.url) ``` Only `POST /exec` wakes a suspended box; the other routes answer `box_not_running`. Every route, and how paths are read, is on [API reference: The edge](https://prized.dev/docs/api#the-edge); the box's coding agent has its own, on [Prompt an agent remotely](https://prized.dev/docs/remote-agents#the-edge-api). History is read from the control plane instead ([API reference: Command history](https://prized.dev/docs/api#command-history)), so it needs no running box. `GET /exec/{processId}/stream` follows a detached process as Server-Sent Events, with byte offsets you reconnect from: ```bash curl -sN -H "Authorization: Bearer $PRIZED_API_KEY" "$EDGE/v1/box/ci-42/exec/p_3f9c0a1b2d4e/stream?stdoutOffset=0&stderrOffset=0" # event: attached # id: 0:0 # data: {"processId":"p_3f9c0a1b2d4e","stdoutOffset":0,"stderrOffset":0} # # event: stdout # id: 13:0 # data: {"data":"compiling...\n","offset":0,"nextOffset":13} # # event: exit # id: 13:0 # data: {"status":"exited","exitCode":0,"signal":null,"stdoutOffset":13,"stderrOffset":0} ``` A `heartbeat` event comes after 15 seconds of silence; after 10 minutes the stream sends `reconnect` with the offsets and ends, so open it again from there (an `EventSource` resumes by itself from the `id:` line). Reading from the box's own log, a reconnect never skips or repeats a byte. ## Errors [#errors] A failed request answers `{"ok": false, "error": {"code", "message"}}`; every code is on [API reference: Errors](https://prized.dev/docs/api#errors). ## Limits [#limits] Caps for runs, files, and the edge API, request rate included, are on [Limits](https://prized.dev/docs/limits#commands-and-files). --- # Desktop > A full Linux desktop, or a single Chrome window, from any box in your browser, streamed over VNC inside the private tunnel with nothing opened to the internet. URL: https://prized.dev/docs/desktop ## Open [#open] On the box page's **Desktop** tab, pick **Desktop** (XFCE) or **Browser** (one Chrome window), then click open. From the terminal: ```bash prized desktop prized browser --url https://example.com ``` It is up in a few seconds; a box created before September 2, 2026 installs the packages on its first open (about two minutes, once). | Command | What it does | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prized desktop [BOX]` | Start the desktop, forward its VNC port to `127.0.0.1`, and open the dashboard tab. Runs until Ctrl-C; wakes a suspended box. | | `prized browser [BOX]` | The same with one Chrome window. `--profile NAME` picks the [profile](#browser-profiles), `--url URL` the start page. | | `--no-open` | Print the `vnc://127.0.0.1:PORT` address and the password for a native VNC client instead of opening the dashboard. | | `--stop` | Stop the desktop (or browser) on the box and exit. Never wakes a paused box. | | `--json` | Print `{"ok":true,"box":"…","mode":"desktop","localPort":…,"password":"…","dashboardUrl":"…"}` once the forward listens, open nothing, keep forwarding. | ## What you get [#what-you-get] * **Desktop:** XFCE with a terminal, a file manager and Chrome; `xdotool` and `wmctrl` are installed for scripts. * **Browser:** one maximized Chrome window and nothing else; sign in to a site once and tools on the box can reuse the profile. * **Keyboard and mouse** work as in a native window; the tab's header has full screen and two clipboard buttons, paste to the box and copy from it. * **Closing the tab leaves the desktop running**, with the same windows when you reopen it; **Stop desktop** in the tab, or `prized desktop --stop`, ends it. ## Let an agent drive it [#let-an-agent-drive-it] An agent on the box can use a web page or any GUI app on its own, from its shell; nothing needs to run on your machine. The tools below come with the box image; on a box created before September 2, 2026 whose desktop was never opened, and for `scrot` on older images, install them first: ```bash sudo apt-get update && sudo apt-get install -y xvfb openbox xdotool wmctrl scrot ``` If `google-chrome-stable` is missing too, install it with `curl -fsSL -o /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && sudo apt-get install -y /tmp/chrome.deb`. **Web pages:** headless Chrome needs no screen and is the most reliable. ```bash google-chrome-stable --headless=new --user-data-dir=/tmp/agent-headless --window-size=1280,800 --screenshot=/tmp/page.png https://example.com ``` For clicks and forms, Playwright works from Python, which the box has (Node is not preinstalled): `python3 -m venv ~/pw && ~/pw/bin/pip install playwright`, then launch the installed Chrome with `channel="chrome"`. **Any GUI app:** start a screen of its own, with a window manager so `wmctrl` sees the windows, then run the app on it. ```bash Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp & sleep 1; DISPLAY=:99 openbox & ``` | Task | Command | | ------------------- | ------------------------------------------------------------------------------------------ | | Run an app | `DISPLAY=:99 google-chrome-stable --user-data-dir=/tmp/agent-chrome https://example.com &` | | Screenshot | `DISPLAY=:99 scrot -o /tmp/screen.png`, then read the PNG | | Click | `DISPLAY=:99 xdotool mousemove 960 540 click 1` (`click 3` for the right button) | | Type and press keys | `DISPLAY=:99 xdotool type 'hello'`, `DISPLAY=:99 xdotool key ctrl+l Return` | | Windows | `DISPLAY=:99 wmctrl -l`, `DISPLAY=:99 wmctrl -a 'Google Chrome'` | To watch the agent work, open the desktop (`prized desktop` or the **Desktop** tab) and have it use `DISPLAY=:10`, without starting a screen of its own. Give your agent this prompt: ```text title="Prompt" This Linux box has no screen attached. If a command below is missing, run `sudo apt-get update && sudo apt-get install -y xvfb openbox xdotool wmctrl scrot` first. For a web page, prefer headless Chrome: google-chrome-stable --headless=new --user-data-dir=/tmp/agent-headless --window-size=1280,800 --screenshot=/tmp/page.png URL, then read the PNG. For an interactive browser or any other GUI app, start a virtual screen once with `Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp & sleep 1; DISPLAY=:99 openbox &`, run the app with DISPLAY=:99, and work in a loop: take a screenshot with `DISPLAY=:99 scrot -o /tmp/screen.png` and read the PNG, then act with xdotool (`mousemove X Y click 1`, `type 'TEXT'`, `key ctrl+l`) and wmctrl (`-l`, `-a NAME`), all with DISPLAY=:99. Take a new screenshot after each step to check it worked. ``` ## Browser profiles [#browser-profiles] A profile is Chrome's user data directory at `~/.prized/browser/` on the box (`default` when you name none): sign-ins, cookies and extensions persist across restarts and ride a [snapshot](https://prized.dev/docs/boxes#snapshots). Names use letters, digits, `-` and `_`, up to 64 characters. Starting the browser with a different profile replaces the running window; starting it again with the same profile and a URL opens that page as a new tab. ## Pause and resume [#pause-and-resume] On Nano through Small the desktop resumes as it was; Medium and larger start a fresh one, and files, packages and profiles survive either way ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). > A running desktop may count as activity for [auto-pause](https://prized.dev/docs/boxes#auto-pause) even with nobody connected; if you rely on auto-pause, stop the desktop when you are done. ## Security [#security] * **Loopback only.** The VNC server listens on the box's loopback; nothing new is reachable from the internet. * **A random password per start**, kept only on the box in a file only your user can read. The dashboard uses it for you; `--no-open` and `--json` print it, and the default output shows only the forwarded `vnc://` address. * **The VNC port** (5910 for the desktop, 5911 for the browser) appears in `prized ports` like any listener, but needs no mirroring: the desktop command forwards it itself. ## Limits [#limits] * VNC, not video: responsive for editors, terminals and browsing, not full-motion video. No audio. * Fixed 1920x1080, scaled to your window. The desktop is drawn without a GPU, on [GPU boxes](https://prized.dev/docs/gpu-boxes) too. * Agents and scripts drive the desktop, or a screen of their own, from a shell ([Let an agent drive it](#let-an-agent-drive-it)): `DISPLAY=:10` is the desktop, `DISPLAY=:11` the browser window. * One desktop and one browser window per box. ## Troubleshooting [#troubleshooting] | Symptom | What to do | | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `desktop install failed` | On the box, `cat ~/.prized/desktop/install.log` names the cause (network, or a held apt lock); open the tab again. | | A black screen after connecting | The session is still starting; if it stays black, **Stop desktop** and open again. | | `the box refused the VNC password` | A stale password after a restart; open the tab again. | | No Chrome window in browser mode | `cat ~/.prized/desktop/browser-session.log` on the box; a profile in use by another Chrome blocks a second one. | | Shortcuts go to your own browser | Use full screen; Cmd/Ctrl+W, Cmd/Ctrl+T and the like always stay with your browser. | | `prized desktop` says the box is unreachable | Same tunnel as `prized ssh`: run `prized doctor`. | --- # Agents > Start a coding agent on your box, close the laptop, and let it work; it is an ordinary process on a machine you already pay for, so nothing it does is metered. URL: https://prized.dev/docs/agents ## Start an agent [#start-an-agent] ```bash prized agents run claude ``` This starts Claude Code in a detached tmux session named `claude`, in your synced project, and prints the reattach command; closing the lid changes nothing. A sleeping or paused box wakes first. `run` never starts a second copy of a session that exists; it says so. | Flag | What it does | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `--attach` | Land in the session as it starts; otherwise reattach later with `prized ssh -s claude`. | | `--dir` | Working directory. Defaults to your only synced project; a [clone](https://prized.dev/docs/sync#add-a-project) is not one, so pass `--dir ~/code/`. | | `--session` | Session name, for a second concurrent run of the same agent. | | `-- ARGS` | Everything after `--` goes to the agent. | > An agent printing in a terminal session, as `run` starts it, holds off [auto-pause](https://prized.dev/docs/boxes#auto-pause). A headless agent (`prized prompt`, a desktop app, a script), or a long command that prints nothing, counts only while it keeps the CPU or network busy; before a long one, switch auto-pause off: `prized box auto-pause BOX off`. What a pause does to an agent depends on the box size ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). The agent's own copy action (Claude Code: select, then **c**) lands on your clipboard through `prized ssh` ([Copy out](https://prized.dev/docs/sessions#copy-out)). ## The agents [#the-agents] | Agent | On the box | Runs on the box | | ----------------------- | ---------------------------------------------- | --------------- | | Claude Code (`claude`) | Preinstalled. | Yes | | Codex CLI (`codex`) | Preinstalled. | Yes | | OpenCode (`opencode`) | Installed on first use, from a pinned release. | Yes | | pi (`pi`) | Installed on first use, from a pinned release. | Yes | | Cursor (`cursor`) | Installed on demand (`--install-cursor`). | Yes | | ChatGPT app (`chatgpt`) | Drives `codex` over SSH from your machine. | No | `chatgpt` is a connection, not a process on the box, so its tasks stop when your machine sleeps and `prized agents run chatgpt` refuses; `prized agents run codex` survives a closed laptop. App setup is on [Codex remote](https://prized.dev/docs/codex). ### OpenCode and pi [#opencode-and-pi] ```bash prized agents run opencode ``` The first `prized agents run` or `prized prompt` of either installs a pinned release on the box, checked against its SHA-256 before anything is unpacked; a mismatch installs nothing and the command says why. A copy you installed on the box yourself is used instead. Neither has a handoff: sign in on the box (`opencode auth login`; or `pi`, then `/login`), or set a provider key as a Secret ([Keys for other agents and backends](https://prized.dev/docs/credentials#keys-for-other-agents-and-backends)). OpenCode also runs its free models with no key. ## See what is set up [#see-what-is-set-up] ```bash prized agents ls ``` One row per agent: installed and signed in on your machine and on the box, MCP servers found, and where it is running. For OpenCode and pi, signed in on the box means a sign-in or a provider key there. Read-only; takes `--json`. ## Hand credentials to the box [#hand-credentials-to-the-box] ```bash prized agents handoff claude codex ``` Lists what would travel, asks, then copies the named agents' sign-in and MCP config to the box; `prized setup` runs the same step. Re-run it after changing MCP config. `--gh` adds your GitHub CLI token and git identity, `--install-cursor` installs Cursor's CLI first; what travels per tool is on [Credentials](https://prized.dev/docs/credentials). ## Drive it remotely [#drive-it-remotely] ```bash prized prompt --provider claude "fix the failing tests and run them again" ``` `prized prompt` runs Claude Code, Codex, OpenCode or pi headless on the box and streams its events until the verdict; `prized events` follows a run and `prized interrupt` stops it, also over HTTP ([Prompt an agent remotely](https://prized.dev/docs/remote-agents)). A model written `amazon-bedrock/…`, `openrouter/…` or `deepseek/…` runs it on that backend ([Models and backends](https://prized.dev/docs/remote-agents#models-and-backends)). ## Several agents at once [#several-agents-at-once] Each agent gets one session by default (`claude`, `codex`, `opencode`, `pi`, `cursor`), each in its own project via `--dir`. For two runs of the same agent, name the sessions: ```bash prized agents run claude --session claude-api --dir ~/code/api prized agents run claude --session claude-web --dir ~/code/web prized ssh --ls ``` ## Bring your own agent [#bring-your-own-agent] Any CLI agent runs on the box. Pick `other` in setup's agent step and it prints these steps with your box's name filled in: 1. `prized ssh` opens the box, waking it if needed. 2. Install the agent's CLI there. 3. Sign in there, once per box; the credential stays on the box disk and Prized never sees it. 4. Run it detached in a named session: `prized ssh -s myagent`, or `tmux new-session -d -s myagent 'the-agent'` from any box shell. Reattach with `prized ssh -s myagent`. For an API key, prefer a [Connector](https://prized.dev/docs/connectors): the box gets a placeholder and the real value never touches its disk. The handoff and `prized agents run` do not cover your own agent; sessions work the same. ## Review from your machine [#review-from-your-machine] In a synced project, the agent's edits stream into the folder on your machine as it works, so you review the diff in your own editor. The box wins [sync](https://prized.dev/docs/sync#conflicts-the-box-wins) conflicts, so a local edit that races the agent yields to its work; a clone has no mirror, so review it over git. > An agent on your machine can run the Prized setup itself with the prompt on [Agent setup](https://prized.dev/docs/agent-setup); for scripts, every CLI command speaks JSON ([CLI](https://prized.dev/docs/cli#for-agents-and-scripts)). --- # Prompt an agent remotely > Prompt Claude Code, Codex, OpenCode or pi on your box from your machine or any script, on the model and backend you pick, follow the run as it happens, and interrupt it; the run lives on the box, so nothing depends on your laptop staying open. URL: https://prized.dev/docs/remote-agents ## Prompt the agent [#prompt-the-agent] ```bash prized prompt --provider claude "fix the failing tests in app/ and run them again" ``` This starts a headless Claude Code run on the box (`claude -p`, in your home directory), streams its events to your terminal, and exits when the agent is done. The first prompt installs three small helper scripts on the box; the first OpenCode or pi prompt also installs that agent ([OpenCode and pi](https://prized.dev/docs/agents#opencode-and-pi)). | Command | What it does | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `prized prompt --provider claude "…"` | Run Claude Code on the default box and follow it. `--provider codex`, `opencode` or `pi` runs that agent. | | `prized prompt box-a7f3 --provider codex --cwd app "…"` | Run on a named box, in `~/app` (`--cwd` is relative to your home, or absolute). | | `prized prompt --provider claude --model opus --effort high "…"` | Pick the model and the reasoning effort (`low`, `medium`, `high`). | | `prized prompt --provider codex --model deepseek/deepseek-flash "…"` | Run on another backend: `amazon-bedrock/…`, `openrouter/…` or `deepseek/…` ([Models and backends](#models-and-backends)). | | `prized prompt models` | The known `--model` values per agent and backend, with the efforts each takes (`--provider` narrows it). | | `prized prompt --provider claude --continue "…"` | Continue the provider's newest session on the box instead of starting fresh. | | `prized prompt --provider claude --conversation cv_… "…"` | Continue that conversation with the provider you name. See [Conversations](#conversations). | | `prized prompt --provider claude --auto "…"` | Let the agent edit and run commands without approvals. See [Approvals](#approvals). | | `prized prompt --provider claude --detach "…"` | Start the run and print its id; follow it later with `prized events`. | | `prized prompt --provider claude --queue "…"` | Run after the conversation's turn in progress instead of refusing. | | `cat brief.md \| prized prompt --provider claude -` | Read the prompt from stdin. | | `prized prompt conversations` | Every conversation on the box, most recently active first, with its prompt count and whether a turn is running. | | `prized prompt ls` | Every run on the box, newest first (at most 50), with its state and conversation; `--conversation cv_…` keeps to one. | | `prized events --follow` | Follow every run in progress until none is left (or show the newest run); `--conversation cv_…` keeps to one conversation, `--run pr_…` follows one run, `--after N` starts after event N. | | `prized interrupt` | Stop every run in progress (TERM to each one's process group, KILL after 5 s); `--conversation cv_…` stops one conversation's, `--run pr_…` one run. | * **Output:** the agent's text, each tool call and result as one dim line, and a last line with the verdict and event count; `-v` adds the provider's system events. * **Exit code:** 0 when the run ends cleanly; 1 when the provider's last verdict is an error (one it retried past does not count), it exits non-zero, or the run was interrupted; 130 on Ctrl-C, which also interrupts the run on the box (`prized events --follow` leaves the run alone on Ctrl-C). * **The prompt travels as a file**, never on a command line, so it stays out of the box's process list and shell history. * **Several runs at once:** `prized events --follow` names each run as it starts and, while more than one is in progress, starts each line with its conversation id; its exit code is the worst of the runs it followed. * **Waking:** `prized prompt` wakes a suspended box; `prized events`, `prized prompt ls`, `prized prompt conversations` and `prized interrupt` do not. ## Sign in first [#sign-in-first] The agent runs as you, with a credential already on the box. Without one, `prized prompt` refuses with `provider_not_signed_in` and names what to set. It looks in a login shell for: | Agent | Credential | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Claude Code | `~/.claude/.credentials.json` (hand yours over with `prized agents handoff claude`), `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `CLAUDE_CODE_USE_BEDROCK` | | Codex | `~/.codex/auth.json` (`prized agents handoff codex`), `OPENAI_API_KEY` or `CODEX_API_KEY` | | OpenCode | Nothing required: with no key it offers only its free models; `opencode auth login` on the box or a provider key opens the rest | | pi | `~/.pi/agent/auth.json` (`pi`, then `/login` on the box) or any provider key, such as `ANTHROPIC_API_KEY` or `OPENROUTER_API_KEY` | | Any agent on a backend | That backend's key ([Keys for other agents and backends](https://prized.dev/docs/credentials#keys-for-other-agents-and-backends)) | ## Models and backends [#models-and-backends] `--model` takes the agent's own model ids and aliases. Written as `/`, it runs the agent on that backend with the key you set on the box as a Secret or env var: | Prefix | Backend | Claude Code | Codex | Example | | ----------------- | ---------------------------------- | -------------------------------------- | -------------------------------- | ----------------------------------------------- | | `amazon-bedrock/` | Amazon Bedrock in your AWS account | Bedrock inference profile ids and ARNs | Bedrock's `openai.*` models | `amazon-bedrock/us.anthropic.claude-sonnet-4-6` | | `openrouter/` | OpenRouter | Any OpenRouter id | Ids that serve the Responses API | `openrouter/anthropic/claude-opus-5.5` | | `deepseek/` | DeepSeek | `deepseek-flash`, `deepseek-v4-pro` | Same | `deepseek/deepseek-flash` | OpenCode and pi already name models as `provider/model`, so the same strings work for them unchanged, along with every other provider they support (`opencode models` and `pi --list-models` on the box list them). The prefix applies per run and never edits the agent's config files; a model with no prefix runs on whatever the agent is set up for. `prized prompt models` lists known ids per agent and backend. Bedrock grants model access per AWS account and region: a model your account has not enabled fails with Bedrock's error. `--effort` maps onto each agent's own control: `claude --effort`, Codex's `model_reasoning_effort`, `opencode run --variant` (a model without that variant ignores it), `pi --thinking` (clamped to what the model supports). DeepSeek models take `low` and `high`. ## Approvals [#approvals] By default the agent can edit files but not act freely, since a headless run cannot ask you; `--auto` lifts that, which "run the tests and fix them" needs. Each agent draws the line its own way: | Agent | Default | `--auto` | | ----------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Claude Code | `claude --permission-mode acceptEdits`: edits allowed, commands denied | `claude --dangerously-skip-permissions` | | Codex | Its `workspace-write` sandbox: edits and commands inside the working directory, no network | `codex --dangerously-bypass-approvals-and-sandbox` | | OpenCode | Shell commands set to ask, which a headless run answers with a rejection; edits and its other tools as configured | `opencode run --auto`: every permission not explicitly denied is approved | | pi | pi has no approvals, so its `bash` tool is left out: `read`, `edit`, `write`, `grep`, `find`, `ls` | The same tools plus `bash` | MCP tools stay available to OpenCode and pi in both modes. > [!WARNING] > `--auto` means no approvals at all: the agent runs any command and edits any file your account on the box can, with your credentials and synced projects in reach. Use it for work you would let the agent do unattended, and interrupt it when it strays. ## Continuity [#continuity] `--continue` resumes the newest session of the same provider on the box, so "now also update the docs" works; `--conversation` resumes a given one. Each agent resumes in its own way: `claude --resume `, `codex exec resume `, `opencode run --session `, `pi --session-id `. Claude Code and pi keep sessions per working directory, so continue from the same `--cwd`. `prized prompt ls --json` shows each run's `sessionId` and `resumedFrom`. With no earlier session on the box, `--continue` refuses (exit 5, `no_session_to_continue` on the API) rather than starting fresh. ## Conversations [#conversations] Every prompt is a turn of a conversation: one provider session, with the agent's memory of the turns before it. A conversation's id looks like `cv_0123456789ab`; `prized prompt` prints it as the run starts, `--json` and the API answer it as `conversationId`, and `prized prompt conversations` lists them. | Flag | The turn's conversation | | --------------------- | -------------------------------------------------------------------------- | | None, or `--new` | A new one, which runs beside the others. | | `--continue` | The provider's newest conversation on the box ([Continuity](#continuity)). | | `--conversation cv_…` | That one; explicitly name its provider with `--provider`. | * **Choose the provider locally:** `--provider` is required on every prompt, including `--conversation`. If omitted, the CLI exits 2 before reading stdin or contacting the box. Conversation files on the box are mutable, so they cannot authorize which agent receives your next prompt. This does not protect prompts or credentials from a compromised box. * **One provider per conversation:** a prompt for a conversation with another `--provider` refuses (exit 5, `provider_mismatch`). All four agents hold conversations the same way, and the first turn of OpenCode or pi installs it if needed. * **Model per turn:** each turn takes its own `--model` and `--effort`, a [backend](#models-and-backends) included; the conversation lists its newest turn's model. * **Pauses:** conversations are files on the box, so they survive a pause and ride snapshots and forks; continue one by id afterwards. ## Parallel runs and the queue [#parallel-runs-and-the-queue] A box runs several conversations at once, up to its limit: one run in progress per 2 GB of the box's memory, at most 16 ([Limits](https://prized.dev/docs/limits#prompting-an-agent-remotely)). A new run beyond it answers `prompt_limit_reached` (exit 6). To change the limit, write a number from 1 to 64 to `~/.prized/prompts/max-runs` on the box; each run is a full agent process, so raise it only on a box with memory to spare. A conversation takes one turn at a time. A prompt for a conversation with a turn in progress answers `prompt_in_progress` (exit 6); with `--queue` it waits and runs when that turn ends, continuing its session. `--queue` with no other flag joins the provider's newest turn in progress, so a `--queue "then lint"` right after a prompt still follows it; with nothing in progress it starts a new conversation. Two commands on the box do the same, so a setup script or the agent itself can drive it: ```bash queuePrompt "then run the linter and fix what it finds" stopAgent ``` | Command | What it does | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `queuePrompt "…"` | Queue a prompt (from its arguments, or stdin): inside a run, behind that run's conversation; elsewhere behind the newest run in progress; with nothing running, after the next run to finish. `PRIZED_CONVERSATION=cv_… queuePrompt "…"` picks the conversation. | | `stopAgent` | Stop every run in progress; `stopAgent cv_…` stops one conversation's. | Both are in `~/.prized/bin`, on the PATH of login shells after the first prompt. A run sees its own conversation and run id as `PRIZED_CONVERSATION` and `PRIZED_RUN`. * **A queued prompt keeps its request:** a `--queue` prompt keeps its own model, effort, working directory and `--auto`; a `queuePrompt` one inherits them from the run it follows. * **An interrupted run** leaves its conversation's queue for that conversation's next turn. ## Where a run lives on the box [#where-a-run-lives-on-the-box] Each run is a directory on the box, so it survives a pause, rides a snapshot, and reads with `ls` and `tail -f`: | File in `~/.prized/prompts//` | Contents | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prompt.txt`, `meta.json` | The prompt as given; provider, model, reasoning effort, and whether `--auto` was on. | | `cmd`, `next` | The provider command line for this run, and the one a continuation uses. | | `cwd`, `pid`, `started_at`, `exit` | The working directory, the process group id, the start time, the exit code when done. | | `events.jsonl`, `stderr.log` | The provider's raw event stream (`claude --output-format stream-json`, `codex exec --json`, `opencode run --format json`, `pi --mode json`) and its stderr. | | `session`, `resume`, `parent` | The provider's session id once seen; for a continued run, the session it resumed; for a queued one, the run it followed. | | `conversation` | The conversation the run is a turn of. | | `interrupted` | Present when `prized interrupt` or `stopAgent` stopped the run. | `~/.prized/prompts/conversations//queue/` holds a conversation's queued prompts, one directory each; `~/.prized/prompts/queue/` holds the ones queued with nothing running. > Run directories are never deleted for you: `rm -r ~/.prized/prompts/pr_…` on the box when you are done with one. ## JSON and JSON Lines [#json-and-json-lines] `--json` on `prized prompt` prints the started run as one object and does not follow. `--jsonl` follows and prints one normalized event per line; with `prized events --jsonl`, it is the only command that prints more than one JSON document. A follow of several runs (`prized events --follow` without `--run`) adds `runId` and `conversationId` to each event. ```json {"seq": 7, "type": "tool_use", "text": "Let me run the suite.", "tool": {"name": "Bash", "input": {"command": "npm test"}}, "raw": {…}} ``` | Field | Meaning | | ------ | -------------------------------------------------------------------------------------------------------------------------------- | | `seq` | The event's line number in `events.jsonl`, from 1; paging and `--after` use it (a page holds up to 200 events or 1 MiB). | | `at` | The provider's timestamp, when the line carries one. | | `type` | `system`, `assistant`, `tool_use`, `tool_result`, `result`, `error`, or `raw` for a line the mapping does not know. | | `text` | The readable content: the agent's text, a command, a tool's output, the final answer, an error (capped at 8 KiB). | | `tool` | For tool events, `{name, input}`; Codex's are `command_execution`, `file_change`, `mcp_tool_call` (`server/tool`), `web_search`. | | `raw` | The provider's line as written. | The agents differ in what they report, and the mapping keeps one line per event: * **OpenCode** reports a tool call once, when it finishes, so each is one `tool_result` carrying the tool, its input and its output. The last step's finish is the `result`; an error event is `error`. * **pi** streams token deltas as `system` events with no text; each finished message is one `assistant` event, tool calls are `tool_use` then `tool_result`, and the run's final answer is the `result`. A failed model call is an `error` event, and pi may retry past it; a run whose last model call failed exits 1, so its status is `failed` in `prized prompt ls` and the API too. A run object (`prized prompt --json`, `prized prompt ls --json`, `prized events --json`) has `id, provider, model, reasoningEffort, auto, status (running, done, failed, interrupted), exitCode, pid, cwd, startedAt, finishedAt, sessionId, resumedFrom, parent, eventCount, prompt`. Runs also carry `conversationId`. A conversation object (`prized prompt conversations --json`, under `conversations` beside `running` and `limit`) has `id, provider, model` (its newest turn's), `prompts` (turns so far), `queued`, `running`, `lastRunId, lastStatus, sessionId, lastPrompt` (the first 160 bytes), `createdAt, updatedAt`. ## The edge API [#the-edge-api] The same surface is HTTP for scripts that hold an API key, under `$EDGE/v1/box/{box}/` with the key as the bearer, found as for [running commands](https://prized.dev/docs/exec#the-edge-api): | Route | What it does | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `POST /prompts` | Starts or queues a run; `new`, `conversationId` and `continue` pick its conversation. The only route that wakes a suspended box. | | `GET /prompts` | Lists runs; `conversation=cv_…` keeps to one. | | `GET /prompts/conversations` | Lists conversations. | | `GET /prompts/{runId}/events` | Pages the events, or streams them as Server-Sent Events (`follow=1`, or `Accept: text/event-stream`); a stream holds one of the token's concurrent request slots until the run ends. | | `GET /prompts/events` | Streams every run in progress as Server-Sent Events until none is left; `conversation=cv_…` keeps to one. | | `POST /prompts/{runId}/interrupt` | Stops a run. | | `POST /prompts/interrupt` | Stops every run in progress; `conversation=cv_…` stops one conversation's. | Bodies and answers are on [API reference: Prompts and events](https://prized.dev/docs/api#prompts-and-events). ## Errors [#errors] A failed request answers `{"ok": false, "error": {"code", "message"}}`. Beyond the shared edge codes, these routes add `invalid_provider`, `prompt_required`, `invalid_request`, `prompt_in_progress`, `provider_not_signed_in`, `provider_not_installed` and `no_session_to_continue` ([API reference: Errors](https://prized.dev/docs/api#errors)). `provider_not_installed` also answers an OpenCode or pi install that failed, with the reason (no network, or a checksum that no longer matches the pin). From the CLI, a run in progress exits 6 (`CONFLICT`) and the rest exit 5 (`CHECK_FAILED`) with a hint. Conversations add `prompt_limit_reached` (the box is at its limit; exit 6 from the CLI) and `provider_mismatch` (`conversationId` names a conversation of another provider; exit 5), and `prompt_in_progress` names the conversation whose turn is in progress. ## Limits [#limits] Every cap for prompts, runs, and events is on [Limits](https://prized.dev/docs/limits#prompting-an-agent-remotely). ## Compared with `prized agents run` [#compared-with-prized-agents-run] [`prized agents run`](https://prized.dev/docs/agents) starts the agent interactively in a tmux session you attach to and approve things in; `prized prompt` is the headless form, with a verdict at the end and an API for scripts. Both use the box's sign-in, and both install OpenCode and pi on first use. --- # Codex remote > Run Codex on your box from the ChatGPT desktop app, with every edit syncing back to your Mac. URL: https://prized.dev/docs/codex Picked the ChatGPT app in [`npx prized`](https://prized.dev/docs/terminal-signup) on a Mac with the app installed? Your box and synced folder are already there: open the app and pick the project. If you installed the app afterwards, add the box below. ## Add your box [#add-your-box] With Prized set up on this Mac ([Quickstart](https://prized.dev/docs/quickstart)), open **Settings > Connections** in the ChatGPT app: 1. **Click Add** under SSH connections from this Mac. ![The Connections page in the ChatGPT app's settings, with an Add button under SSH connections from this Mac](https://prized.dev/docs/codex/ssh-add.webp) 2. **Tick your box** and click **Add**. It is listed by its name. ![The Add SSH Connection dialog listing the box, fig, with a checkbox](https://prized.dev/docs/codex/ssh-pick.webp) 3. **Check it says Connected**, then pick a project on the box; synced projects are in `~/code`. ![The saved connection, fig, switched on and Connected](https://prized.dev/docs/codex/ssh-connected.webp) > Skipped ChatGPT during setup? Run `prized setup --agent chatgpt` to sign Codex in on the box. The box gets its own sign-in, so it never signs out the app on your Mac. ## From your phone [#from-your-phone] The ChatGPT mobile app connects to the box directly, no Mac needed: 1. Open the box in the [dashboard](https://prized.dev/dashboard), go to **Settings**, and click **Set up** beside **Phone access**. Generate the password; it is shown once. 2. In the ChatGPT app, open **Settings > Connections > Add SSH host** and paste the host, port, username, and password. * **One password per box.** A new one replaces the old, and **Revoke** ends it. * **Connecting wakes a paused box.** * **A busy gateway may close a new connection.** Wait briefly and reconnect with the same phone password; this does not revoke it. * **Terminal only.** No port forwarding; use `prized ports` on your Mac. * **Idle connections close** after about fifteen minutes; the app reconnects on its own. No **Add SSH host**? OpenAI is still rolling it out; update the app and look again. --- # Claude Desktop remote > Run Claude Code on your box from the Claude desktop app, with nothing to set up on the box. URL: https://prized.dev/docs/claude-desktop Picked Claude desktop in [`npx prized`](https://prized.dev/docs/terminal-signup) on a Mac with the app installed? Your box is already there: pick ` (Prized)` from the environment menu in the Code tab. If you installed the app afterwards, add the box below. ## Add your box [#add-your-box] With Prized set up on this computer ([Quickstart](https://prized.dev/docs/quickstart)), open the Code tab: 1. **Click Local**, then **SSH > Add SSH connection**. ![The environment menu above the prompt box, open on SSH, with Add SSH connection in its submenu](https://prized.dev/docs/claude-desktop/ssh-menu.webp) 2. **Type your box's name** as the Name and the SSH host, and click **Add SSH connection**. Leave everything else empty. ![The Add SSH connection dialog with the box's name, fig, in both the Name and SSH host fields](https://prized.dev/docs/claude-desktop/ssh-dialog.webp) 3. **Click Select folder** and pick a project on the box; synced projects are in `~/code`. ![The prompt box with fig in place of Local, and Select folder beside it](https://prized.dev/docs/claude-desktop/ssh-connected.webp) > Asked for a password? Run `prized login` on this computer, wait a minute, and try again; `prized doctor` finds anything else. ## What the app can do [#what-the-app-can-do] The app signs in with a key Prized adds for this computer, and that key works only through your Prized tunnel. It can run commands, transfer files, and open a terminal; to forward ports, use [`prized ports`](https://prized.dev/docs/ports). ## Remove a computer [#remove-a-computer] Revoke the computer's [API key](https://prized.dev/docs/workspaces#api-keys), or remove the member from the [workspace](https://prized.dev/docs/workspaces), and every box drops its key within seconds. A computer unused for 45 days loses its key until it connects again. --- # Boxes > Create, pause, resize, move, and delete boxes: dedicated Linux machines from Nano to Extra Large. URL: https://prized.dev/docs/boxes ## Create [#create] ```bash prized box new # a fruit name, the size your balance runs comfortably prized box new mango --size Small --ttl 4h --wait ``` Create from the CLI (flags under [CLI: box new](https://prized.dev/docs/cli#box-new)) or the dashboard's [New box](https://prized.dev/docs/dashboard#new-box) page. A Small box with no name, snapshot, or environment is usually running when the create returns; any other new box usually boots in under a minute, a box restored from a snapshot or forked takes minutes, and `--wait` blocks until it is running. Sizes mix freely, each at its own rate ([Billing](https://prized.dev/docs/billing)); a size your balance could not run for four hours cannot be picked unless [overage](https://prized.dev/docs/billing#overage) is on. `prized box ls` lists your boxes, `prized box info mango` shows one in full, and [Machines](https://prized.dev/docs/machines) says what each size is made of. ## States [#states] | State | Meaning | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | running | Up and reachable, except briefly after a failed restart (see [Resize](#resize)). | | provisioning | Being created or booted. Usually under a minute; minutes for a restore or a fork. | | suspended | Paused. Compute is stopped; the disk stays. | | waking | Coming back from a pause. | | deep sleep | Suspended over 14 days: the disk is parked as a snapshot. Waking takes minutes. | | resizing | Changing size. Returns to running, or to suspended if paused. | | migrating | Moving to another region; see [Regions](#regions). | | degraded | The box stopped reporting; it reboots itself. | | provision failed | Never came up; the reason is in its events. Costs nothing and cannot be woken: delete it and create again. | ## Pause and resume [#pause-and-resume] ```bash prized box pause mango prized box resume mango --wait ``` The disk survives a pause (files, tools, git state), and billing drops to the sleep rate at once. Running processes depend on the size: | Size | On pause | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | Nano through Small | **Memory snapshot** (hibernate). Processes, tmux sessions, and half-finished builds resume where they were. | | Medium and larger, [GPU boxes](https://prized.dev/docs/gpu-boxes) | **Clean stop.** Files survive; processes restart on resume. | | [Infinite storage](#infinite-storage), any size | **Clean stop**, as above. | `prized ssh` wakes a suspended box on connect. A box's [desktop](https://prized.dev/docs/desktop) follows the same table. A resume that cannot bring the box back ends in a pause: the box keeps its disk, bills at the sleep rate, and its events show `reconciler:wake_failed`. Resume it again; if it still will not start, email the support address shown on https://prized.dev/docs/troubleshooting. ## Auto-pause [#auto-pause] Set per box: **Auto-pause** on the box's dashboard page (30 minutes to 24 hours), `prized box auto-pause mango 2h` (up to 7 days), or `prized box new --auto-pause 45m`. The box pauses itself once nothing has used it for that long. Off by default on a plan. On the [free credit](https://prized.dev/docs/billing#free-credit) it is on by default: a box pauses after **2 hours** idle, and **never in its first 24 hours**. Change the window or switch it off the same ways (`prized box auto-pause mango off`); a window you set replaces the default, and a plan lifts the default. * **Counts as use:** typing or output in any terminal session, so an agent working in tmux or a shell keeps the box up; sustained CPU (builds, agents, test loops); and sustained network traffic in or out of the box: a sync, a clone or download, a container image pull, a port you are using. A forgotten shell prompt does not, and neither do the box's own background check-ins or traffic between containers on the box. * **A large first sync can look idle** if it stalls, for example while your laptop sleeps; on a plan, set the window once the first sync finishes. * **Waking it:** `prized ssh`, `prized up`, `prized exec`, `prized agents run`, **Resume** or **Open terminal** on the dashboard, and a members link to one of its [ports](https://prized.dev/docs/ports). An editor's Remote-SSH, a plain `ssh` to the box, and sync do not wake a paused box; they report it as not running. Run `prized box resume mango --wait`, then reconnect them. * **The 30 minute floor** is how long the box waits before calling itself idle; the box page shows the idle time so far. * **It is exactly a pause**, shown in the event list as `reconciler:auto_pause`. * **A workspace policy can cap the window** ([auto-pause floor](https://prized.dev/docs/teams#policies)), except on the free credit; [quiet hours](https://prized.dev/docs/teams#quiet-hours) pause the whole workspace nightly. ## Pause deadline [#pause-deadline] ```bash prized box new ci-run --ttl 2h # pauses two hours from now prized box extend mango --ttl 12h # set, or push back, the deadline prized box extend mango --no-deadline ``` A deadline pauses the box at a set time, busy or idle: `--ttl` at create, `prized box extend` later, or **Pause at** on the box page's **Settings** tab, 5 minutes to 30 days ahead. * **One pause, not a schedule.** It clears when it fires or the box wakes. A new `--ttl` replaces it, counting from now; `--no-deadline` removes it. * **It needs a running box** to set; clearing is always allowed. * **It stacks with auto-pause:** whichever comes first pauses the box. The event list shows it as `reconciler:pause_deadline`. ## Resize [#resize] ```bash prized box resize mango Medium ``` The box stops, changes size, and starts again with files intact, metered at the new rate from that moment; it stays `resizing` until it is ready to connect. Any size your balance can keep running is allowed. | Case | What happens | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sizing up | The disk grows in the same restart. Rarely, a just-grown disk must wait about six hours to grow again; the resize still lands and the space appears on the next restart, unless the new size has more memory than the disk. Then the box restarts at its old size, shown as `reconciler:resize_rolled_back` in its events; resize again after the wait. | | Sizing down | The disk stays as it is (disks never shrink) and keeps costing its own sleep rate. | | A suspended box | Resizes fine and takes the new size when it next starts. | | A box creating, resizing, or moving | Refused. | | Onto a GPU size | From a box created as Medium or larger; Nano through Small, and boxes that started as one, fork onto one instead ([GPU boxes](https://prized.dev/docs/gpu-boxes#move-a-box-onto-a-gpu)). | | The restart fails | The box can briefly show `running` before `degraded`; it is not ready to connect. | | A bigger disk than the size normally has | Priced with that disk, so the resize check quotes what you will actually pay. | ## Regions [#regions] Boxes run in US West (Oregon), the default, US West (N. California), US East (N. Virginia), US East (Ohio), and Europe (Frankfurt); [Machines](https://prized.dev/docs/machines#regions) lists their ids and CLI names. The dashboard and the onboarding tour start a new box in the region nearest you, with **Region** on the [New box](https://prized.dev/docs/dashboard#new-box) form to pick another: its menu lists every region fastest first, by the round trip timed from your browser. The CLI and API default to Oregon; pick one with `prized box new --region eu-central-1` or `"region": "us-east-1"` on [`POST /api/v1/boxes`](https://prized.dev/docs/api#boxes). A box a plan starts runs in the region picked with its size ([Billing](https://prized.dev/docs/billing#choosing-and-changing-the-plan)); a box restored from a snapshot starts in the snapshot's region. Move an existing box with **Region** on the box page or `prized box move mango us-east-1`. [GPU boxes](https://prized.dev/docs/gpu-boxes) run in Oregon only during their beta. * **The box stays up while the disk copies**, then goes offline for a few minutes and returns in the new region with its name, files, tools, vars, and secrets. Its public IP changes; `prized ssh` follows it. * **Metering continues at the normal rate**, and a size costs the same in every region. * **Only a running box moves**, one move at a time. A move that cannot finish leaves the box in its original region on its original disk. * **One move per box in 24 hours**, and **three per workspace**, counting only moves that ran; moving needs a funded balance. For a new box, create it in the other region instead. * **A region holds the box, not the account.** The box's disk, snapshots, and daily backups stay in its region. Your account, box settings, and [command history](https://prized.dev/docs/exec#command-history) are kept in the US (`us-west-2`), and the web terminal and [shared port](https://prized.dev/docs/ports) links pass through there too; `prized ssh` connects to the box directly. * **Outside Oregon a new box always boots fresh**, which takes a minute or two; in Oregon a Small can start from one already booted. ## Snapshots [#snapshots] Snapshot a box's disk without stopping it, restore into a new box, fork a running box, or set a schedule ([Snapshots](https://prized.dev/docs/snapshots)). Take one before risky changes. ## Infinite storage [#infinite-storage] ```bash prized box new mango --size Small --storage infinite ``` An infinite storage box keeps its files in object storage (Amazon S3) that grows as you write, instead of on a fixed-size disk. The files you are working with stay in a fast cache on the box, so day-to-day work, Docker included, runs at the speed of a normal disk. Ask for it at create with `--storage infinite`, `"storage": "infinite"` on [`POST /api/v1/boxes`](https://prized.dev/docs/api#boxes), or **Infinite storage** on the dashboard's [New box](https://prized.dev/docs/dashboard#new-box) form; without it, a box gets its own disk. The exception is a workspace in the infinite storage alpha, where a box that can have infinite storage gets it unless you pass `--storage standard`. * **Where it is offered.** Extra Small and up, on a fresh box in any of the five regions. Each box's files stay in its own region. A size, a region, or a snapshot source it cannot take, or a workspace it is not open to, answers `422 storage_unavailable` with the reason, and nothing is created; a region it is not offered in is refused with the regions that offer it. * **How much fits.** Ten times its size's disk: Extra Small 1.5 TB, Small 3 TB, Medium 5 TB, Large 10 TB, Extra Large 20 TB. Resizing to a bigger size raises it while the box runs; a smaller size keeps the space it already has. One file can be up to 16 TiB. * **What it costs.** The box's usual price, which includes its size's disk; the space in use beyond that disk is $0.05 a GB-month, awake or asleep, metered from what the box last reported ([Billing](https://prized.dev/docs/billing#how-it-works)). * **Very large writes.** Tens of GB land at disk speed. Writing hundreds of GB in one go slows to the rate the box uploads to S3, about 90 MB/s, until it catches up. * **The box page** shows **Storage: Infinite** and the space in use instead of a disk size. * **Pausing is a clean stop** at every size: files survive, processes restart on resume. A paused box never goes into deep sleep. * **Not available on these boxes:** snapshots (by hand or on a schedule), forks, and region moves. * **A delete can be undone for 7 days,** as on any box: the box's files are kept, and **Restore the box** on the **Deleted** tab (or `prized snapshot restore`) brings them back in a new infinite storage box in the same region, the same size unless you pick another that offers it. The new box starts once its files are copied, which takes longer the more it held ([Data retention](https://prized.dev/docs/data-retention#deleting-a-box)). * **If its storage service fails,** the box reboots itself and is back in about a minute. Saved files are safe; writes from the last few seconds can be lost, as on a laptop that loses power. ## Environments [#environments] An environment is what a new box inherits: repos, a setup script, env vars, secret files, and which credentials the box may receive. Pick one at create time or mark a default; a running box keeps its version until you upgrade it ([Environments](https://prized.dev/docs/environments)). ## Several boxes [#several-boxes] Every box has a hostname: a fruit name unless you pick one (lowercase letters, digits, and hyphens, up to 32 characters). `prized ssh mango` connects to any box. The **default box** is the one `prized setup` connected first (or `prized login`, when your account had exactly one), used when a command names no box. Other boxes are reachable by name; connect one with `--box` for sync, mirrored ports, and every other command. ```bash prized setup --box mango # connect a second box: its own config and daemon prized --box mango status # aim any command at it PRIZED_BOX=mango prized sync ls # the same from the environment ``` * **Adding a box.** `prized setup --box mango` writes `boxes/mango.toml`, starts its daemon, and runs the normal setup; `prized init --box mango` only connects. With no default box yet, either makes the box the default. * **Aiming commands.** `--box NAME` (or `PRIZED_BOX`) works on every command, by hostname, or by box id for `setup` and `init`. `--config PATH` overrides everything, as an escape hatch. * **Changing the default.** `prized init --force --box mango` rewrites the default config (backed up first) and restarts the default daemon. A box with its own file is refused: first `prized --box mango daemon uninstall` and delete `boxes/mango.toml`. * **Same-named projects.** Two boxes cannot sync a project under one name; the second `prized sync add` asks for `--name`. * **The same port on two boxes** shows as `conflicted` in `prized ports ls`; toggle it off on one box, or list it under `[ports].never` in that box's config. | For a box named `mango` | Where | | ----------------------- | ------------------------------------------------------------------------------------------------------ | | Config | `~/.config/prized/boxes/mango.toml`, the same schema as `config.toml` | | Agent token | `~/.config/prized/boxes/mango.token` | | WireGuard key | `~/.config/prized/wg-mango.key` | | Daemon socket and state | `~/Library/Application Support/prized/boxes/mango/prizedd.sock` (macOS and Linux) | | Service | launchd `com.hudware.prizedd.mango`, systemd `prizedd-mango.service`, or the `PrizedDaemon-mango` task | | Log | `prizedd-mango.log` in the usual [log folder](https://prized.dev/docs/cli#files) | On Windows these sit under `%LOCALAPPDATA%\Prized\boxes\`. ## Delete [#delete] ```bash prized box delete mango # asks you to type the name; -y skips the prompt ``` > [!WARNING] > Deleting a box terminates the machine and erases its disk. Anything you synced is already on your machine. A delete can be undone for 7 days from **Recently deleted** on the **Deleted** tab of the Boxes page, except for a box deleted within an hour of being created or under zero data retention ([Data retention](https://prized.dev/docs/data-retention)). --- # Machines > What a box is made of: its size and disk, Ubuntu 24.04 on a dedicated x86_64 virtual machine, preinstalled software, and the Prized processes beside your work. URL: https://prized.dev/docs/machines ## Sizes [#sizes] The live shape and price table is at https://prized.dev/pricing. Pick a size by label, short name, or id (`prized box new --size Small`; `xs`, `s`, `m`, `l`, `xl`; `nano`, `micro`, `lite`, `flow`, `pro`, `max`, `ultra`) or in the create form, and change it with a [resize](https://prized.dev/docs/boxes#resize). Rates and plans are on [Billing](https://prized.dev/docs/billing). The GPU sizes, `gpu-l4` and `gpu-l40s`, are on [GPU boxes](https://prized.dev/docs/gpu-boxes). * **Nano and Micro are burstable**, for editors, agents, and small dev servers rather than sustained full-CPU load; Extra Small and up are fixed-performance. * **Memory decides what a pause keeps:** Nano through Small hibernate, Medium and larger stop clean ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). * **The [free credit](https://prized.dev/docs/billing#free-credit) starts Nano through Small,** one box at a time, and a box started on it can be resized within those four; Medium and larger come with a plan. ## Disk [#disk] One encrypted volume is the box's only disk, holding your home directory, Docker images, and the system. | Fact | Detail | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Type | SSD (EBS gp3) at baseline performance, always encrypted, as are its snapshots | | Size | The Disk column above, 40 GB on a Nano to 2 TB on an Extra Large | | Growing | Sizing up grows the disk in the same restart ([Resize](https://prized.dev/docs/boxes#resize)) | | Shrinking | Never. A box that kept a bigger disk than its size pays that disk's sleep rate, and a snapshot restores only into a size whose disk holds it ([Restore](https://prized.dev/docs/snapshots#restore-into-a-new-box)) | | Paused | The disk stays, at the sleep rate; after 14 days paused it is parked as a snapshot, deep sleep ([States](https://prized.dev/docs/boxes#states)) | | Snapshots | Block-level, incremental images of this disk, taken while the box runs ([Snapshots](https://prized.dev/docs/snapshots)) | ## Operating system [#operating-system] Ubuntu 24.04 LTS, fully upgraded when the box image was built. Your login user has passwordless `sudo`, is in the `docker` group, and can `apt install` whatever is missing. * **Your login user is your first name**: lowercased, accents folded, leading digits dropped, only letters, digits, and hyphens, up to 31 characters (`José` signs in as `jose`). It is `doppel` when nothing usable survives, when the name collides with a system account such as `git`, and on a Small box that was ready before you asked for it ([Boxes](https://prized.dev/docs/boxes)); a box restored or forked from a snapshot keeps its source box's user. `prized box info` shows it. * **Your user services outlive your session.** `systemctl --user` services you enable start when the box boots and keep running after you log out (except on infinite storage boxes). * **Sandboxes work.** `bwrap` has the AppArmor grant the Claude Code and Codex command sandboxes need; Ubuntu's restriction on unprivileged user namespaces otherwise stays on. ## Preinstalled software [#preinstalled-software] A fresh box has all of this from first boot, signed in to nothing; your agents' credentials come from [your machine](https://prized.dev/docs/credentials). | What | On the box | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | Docker Engine, Compose, Buildx | `docker`, `docker compose`, `docker buildx`; the daemon starts at boot, no `sudo` needed | | Claude Code, Codex CLI | `claude`, `codex`; per user, each updates itself in place | | GitHub CLI | `gh` | | Compilers and build tools | `gcc`, `g++`, `make`, `pkg-config`, `git` | | Python | `python3`, `pip`, `venv` | | Shell tools | `tmux`, `mosh`, `rg`, `fd`, `jq`, `htop`, `rsync`, `sqlite3`, `zip`, `unzip`, `zstd`, `curl`, `wget` | | Desktop stack | XFCE, Xvfb, x11vnc, Google Chrome, `xdotool`, `wmctrl`; nothing starts until you open the [desktop](https://prized.dev/docs/desktop) | | Sandboxing | `bwrap` (bubblewrap), with the AppArmor profile above | | Sync agent | Mutagen's agent, fetched on the first `prized sync add` or `prized setup` ([Sync](https://prized.dev/docs/sync)) | Not preinstalled: Node.js, Go, Rust, Homebrew, databases, and the rest of a working environment. `prized setup` installs the versions your machine has ([Bring your environment](https://prized.dev/docs/environment)); for a first boot that repeats across boxes, use an [environment](https://prized.dev/docs/environments). ## Architecture and virtualization [#architecture-and-virtualization] | Fact | Detail | | --------------------- | ----------------------------------------------------------------------------------------------------------------------- | | CPU architecture | x86\_64 (amd64) only; no arm64 sizes. `prized env` rebuilds or re-pulls arm64-only Docker images for amd64 | | Virtualization | One virtual machine per box with its own kernel, not a container or a shared host; Docker works as on any Linux machine | | Nested virtualization | Off; KVM is not available inside a box | | GPU | None on these sizes; [GPU boxes](https://prized.dev/docs/gpu-boxes) have one NVIDIA L4 or L40S | | Cloud credentials | None: a box carries no cloud role, so nothing you run finds provider credentials on it | Each size maps to one EC2 instance type: | Size | Instance type | | ----------- | -------------------------------------------- | | Nano | `t3a.small` (burstable) | | Micro | `t3a.medium` (burstable) | | Extra Small | `m6a.large` | | Small | `m6a.xlarge` | | Medium | `m6a.2xlarge` | | Large | `m6a.4xlarge` | | Extra Large | `m6a.8xlarge` | | GPU L4 | `g6.2xlarge` ([GPU boxes](https://prized.dev/docs/gpu-boxes)) | | GPU L40S | `g6e.2xlarge` ([GPU boxes](https://prized.dev/docs/gpu-boxes)) | ## Regions [#regions] | Region | AWS id | In the CLI | | -------------------- | -------------- | --------------------- | | US West (Oregon) | `us-west-2` | `oregon`, the default | | US West (California) | `us-west-1` | `california` | | US East (Virginia) | `us-east-1` | `virginia` | | US East (Ohio) | `us-east-2` | `ohio` | | Europe (Frankfurt) | `eu-central-1` | `frankfurt` | A box lives in one region and can [move](https://prized.dev/docs/boxes#regions). A size costs the same in every region; a snapshot stays in its box's region. The CLI takes a region by AWS id or by name; CLIs older than these regions know only `oregon` and `california` by name, and take the AWS id for the rest. ## What runs on the box [#what-runs-on-the-box] Beside your own processes, a fresh box runs these Prized services as ordinary systemd units (`systemctl status prized-agent`). | Process | What it does for you | What it can see | | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `prized-agent`, as your login user | Lists listening ports and their processes; samples CPU, memory, disk, and network; lists tmux sessions; judges idleness for [auto-pause](https://prized.dev/docs/boxes#auto-pause); relays loopback-only ports for [mirroring](https://prized.dev/docs/ports); updates itself | A read-only view of the system and process table; never pauses the box itself | | `prized-secretsd`, root, loopback only | The [secrets broker](https://prized.dev/docs/credentials#secrets-for-agents): swaps a placeholder for the real value on requests to the hosts you allowlisted | Secret values in memory only; idle until a secret is mounted | | `sshd`, root, tunnel only | Your [sessions](https://prized.dev/docs/sessions), sync, mount, `prized exec`, and every SSH-speaking tool | Accepts Prized's short-lived certificates, the device keys registered for you, and any key in `~/.ssh/authorized_keys` | | The tunnel, WireGuard and its websocket fallback | The two ports open to the internet, `udp/51820` and `tcp/443` | Encrypted tunnel traffic only | On demand: the Mutagen agent while a project syncs, the desktop stack while a desktop is open, and helper scripts the first [remote prompt](https://prized.dev/docs/remote-agents) installs. The box sends Prized telemetry only (vitals, listening ports, top processes, versions), never your files, terminal output, secret values, or agents' sign-ins ([Security](https://prized.dev/docs/security), [Data retention](https://prized.dev/docs/data-retention#telemetry-retention-windows)). ## Networking [#networking] | Fact | Detail | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Inbound | Only the tunnel's two ports, on the box's own public IPv4 address. SSH is never on the internet: it and every port you run are reached through the tunnel ([Ports](https://prized.dev/docs/ports), [Sessions](https://prized.dev/docs/sessions), [Credentials](https://prized.dev/docs/credentials#the-machine-itself)) | | Public ports | None: no port you run on the box takes connections from the internet. Ports can be shared at a link; see [Ports](https://prized.dev/docs/ports#share-a-port) | | Outbound | Not filtered | | From your machine | WireGuard over UDP 51820. Where UDP is blocked, the same tunnel goes over HTTPS via the Prized relay (`term.prized.dev`) or the box's port 443, whichever gets through, and through your proxy if set (`HTTPS_PROXY`, or a Mac's system proxy) ([Networks that block the tunnel](https://prized.dev/docs/troubleshooting#networks-that-block-the-tunnel)) | | Public IP | Not stable: it changes on a pause and on a [region move](https://prized.dev/docs/boxes#regions); `prized ssh` follows the box | --- # GPU boxes > Boxes with one NVIDIA GPU, an L4 or an L40S, billed by the hour from your prepaid balance. In beta. URL: https://prized.dev/docs/gpu-boxes A GPU box is a box with one NVIDIA GPU: the same Ubuntu machine, CLI, sync, ports and sessions as any [box](https://prized.dev/docs/boxes), with the NVIDIA driver, `nvidia-smi` and Docker's GPU runtime ready. GPU boxes are in beta: write to the support address shown on https://prized.dev/docs/troubleshooting to have your workspace added. ## Sizes [#sizes] GPU L4 (`gpu-l4`): 8 vCPU / 32 GB, 1 × L4, 24 GB GPU, 500 GB disk; $1.25 an hour awake, $0.05 an hour asleep. GPU L40S (`gpu-l40s`): 8 vCPU / 64 GB, 1 × L40S, 48 GB GPU, 500 GB disk; $2.75 an hour awake, $0.05 an hour asleep. * **Billed from your prepaid balance only.** [Overage](https://prized.dev/docs/billing#overage) never covers a GPU box's awake hours, including ones still owed after it pauses or becomes a CPU size: a create, resume or resize onto one needs the balance to run your boxes, it included, for four hours, and at zero it pauses even where overage keeps your other boxes running. Asleep, its disk bills like any box's. * **A paid plan.** A GPU box needs a plan whose first invoice is paid; the free credit or a top-up alone does not start one. * **One GPU box per workspace** during the beta. * **Oregon only** during the beta: GPU boxes run in US West (Oregon), `us-west-2`. A create, fork, resize or resume onto a GPU size in any other region answers `403 gpu_unavailable` with `reason` `region_unavailable`, and a GPU box does not [move regions](https://prized.dev/docs/boxes#regions). Pick Oregon under **Region** in the create form, or `--region us-west-2` in the CLI. * **Disk only.** GPU sizes have the disk in the table, not [infinite storage](https://prized.dev/docs/boxes#infinite-storage). ## Create [#create] ```bash prized box new lab --size gpu-l4 --wait ``` Or pick GPU L4 or GPU L40S in the dashboard's create form. Then check the GPU: ```bash prized ssh lab -- nvidia-smi ``` Containers reach it through Docker's GPU flag: ```bash prized ssh lab -- docker run --rm --gpus all ubuntu nvidia-smi ``` ## Move a box onto a GPU [#move-a-box-onto-a-gpu] Fork it: a new GPU box from a copy of the disk, while the original keeps running. ```bash prized fork lab --tier gpu-l40s ``` A box created as a Medium, Large or Extra Large can also change size in place, and back: ```bash prized box resize lab gpu-l4 ``` Nano through Small keep their memory when they pause, which a GPU machine cannot do, so they fork instead, and so does a box that started as one; the resize says so, or goes back to the size it came from. A GPU box changes size while it runs or sleeps, not while it is resuming. A box's first boot on a GPU installs the driver: a few minutes, with Docker restarted once, and `prized box info` shows `driver installing` until it is done. ## Pause and resume [#pause-and-resume] * **A clean stop.** Files survive; processes restart on resume, as on Medium and larger ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). * **GPU work counts as use.** A GPU kept busy holds off [auto-pause](https://prized.dev/docs/boxes#auto-pause) like a busy CPU does. * **When no GPU is free**, a resume waits about 10 minutes, billed as awake, and then the box goes back to sleep, with `reconciler:wake_gave_up:capacity` in its events. Resume it again later, or fork it, which can start on any free GPU in the region. A resize onto a GPU size bills the size it came from until the GPU is running, and goes back to that size if it cannot get one, or stays paused if that cannot start either. A resize off a GPU size bills the GPU until it is off, and one between GPU sizes bills the GPU it has until the new one is running. * **Back to a CPU size:** resize a paused GPU box to Medium or larger and it resumes on the CPU size, without waiting for a GPU. ## What a GPU box shows [#what-a-gpu-box-shows] `prized box info` has a `gpu` line (the model, utilization, memory in use), and the box's page in the dashboard shows the same under its live numbers. --- # Snapshots > Take point-in-time images of a box's disk by hand or on a schedule, restore them into new boxes, and fork a running box. URL: https://prized.dev/docs/snapshots ## What a snapshot captures [#what-a-snapshot-captures] A snapshot is the box's whole disk at one instant, taken without pausing the box. * **Crash-consistent.** It holds what had reached the disk, as after a power loss, not what was still in memory (an editor buffer, a database's unflushed page). Save or flush first if a moment matters. * **Block level, incremental.** Each stores only what changed since the one before (a new box's first, only what the box itself wrote), so frequent snapshots of a quiet box are cheap. * **Independent of the box.** Deleting the box does not delete its snapshots, and restoring one never changes the box it came from. * **Not on infinite storage.** A box with [infinite storage](https://prized.dev/docs/boxes#infinite-storage) has no disk holding its files, so it cannot be snapshotted or forked (`422 storage_backend_unsupported`). Deleting one still keeps its files for the 7-day undo window ([Data retention](https://prized.dev/docs/data-retention#deleting-a-box)). ## Kinds [#kinds] The box's Snapshots tab and `prized snapshot ls` show each snapshot's kind: | Kind | Who takes it | Counts toward the limit | Goes away | | --------- | --------------------------------------------------- | ----------------------- | ------------------------------------------------------------- | | manual | You, from the dashboard or `prized snapshot create` | yes | When you delete it | | fork copy | `prized fork` or the Fork button | no | By itself, once the new box is running | | automatic | The schedule you set on a box | no | By itself, past the box's keep count or after keep x interval | Fork copies are hidden by default (`prized snapshot ls --kind fork` lists them) and can never be restored. ## Take a snapshot [#take-a-snapshot] ```bash prized snapshot create mango --name pre-upgrade prized snapshot ls ``` The command returns at once; the snapshot shows `creating` until the disk copy completes, then `available`: a minute or two on a quiet box, longer the more was written since the last one. One per box at a time; a second request is refused with `snapshot_in_progress`. In the dashboard: the Snapshots page, or the Snapshot action on the box page. ## Restore into a new box [#restore-into-a-new-box] ```bash prized snapshot restore pre-upgrade --name mango-2 ``` Restore never rolls a box back in place: it creates a new box from the snapshot, in the snapshot's size and region, with the source box's login user. `--tier` picks any size whose disk holds the snapshot; a smaller disk is refused with the numbers. A restore is a create, so the box limit and a new box's balance rules apply. ## Fork a box [#fork-a-box] ```bash prized fork mango --name mango-2 --wait ``` A fork is a snapshot and a restore in one call, while the source keeps running. The new box keeps the source's size, region, login user, and auto-pause setting; `--tier` picks another size whose disk holds the copy. The Fork action on the box page does the same. * **Minutes, not seconds.** The new box waits for the disk copy, then launches and boots; `--wait` prints each step, or watch the box page. * **A copy that stops is a failure; a slow one is not.** The new box waits as long as the copy keeps moving: the first copy of a large, busy disk can take over an hour. No progress for 30 minutes, or not done after 8 hours, marks the new box failed with the reason (delete it; the source is untouched, and the failed box costs nothing). To make forks of a big box quick, take a manual snapshot first: the fork then copies only what changed after it. * **The copy is temporary:** it deletes itself once the new box is running (or has failed for good). `--keep-snapshot` keeps it as a manual snapshot, which counts toward the limit. * **A fork is a create:** if the box limit or the balance rules refuse it, nothing is created. ## Automatic snapshots [#automatic-snapshots] ```bash prized box auto-snapshot mango --every 6h --keep 5 prized box auto-snapshot mango --every off ``` A schedule snapshots the box's disk whenever the newest automatic snapshot is older than the interval; the box page's **Snapshots** tab has the same control. * **When they run.** While the box is running or paused; a box in deep sleep is skipped until it has a disk again, and a manual snapshot in flight defers the scheduled one. * **Retention.** At most `keep` per box, each expiring after keep x interval (every 6 hours keeping 5 is a 30 hour window). Switching the schedule off stops new ones; those already taken still expire, except one a new box is still launching from. * **Outside the limit**, but like any snapshot they need a funded balance or an active plan. Names are `auto--`, in UTC. ## Limits [#limits] Every cap, the 20-snapshot count included, is on [Limits](https://prized.dev/docs/limits#snapshots). ## CLI [#cli] Every command takes `--json` and prints one object. | Command | What it does | | ---------------------------------------------------------------- | ----------------------------------------------------------------------- | | `prized snapshot create [--name N] [--description D]` | Snapshot the box's disk now | | `prized snapshot ls [--box B] [--kind K]` | List snapshots, newest first; `--kind fork` shows transient fork copies | | `prized snapshot restore [--name NEWBOX] [--tier T]` | A new box from a snapshot; `--tier` is any size whose disk holds it | | `prized snapshot delete [-y]` | Delete a snapshot (asks first) | | `prized fork [--name] [--tier] [--keep-snapshot] [--wait]` | A new box from a copy of the box's disk | | `prized box auto-snapshot --every 6h\|off [--keep N]` | Set or clear the box's snapshot schedule | Exit codes follow the [CLI contract](https://prized.dev/docs/cli#for-agents-and-scripts): a busy box, a taken name, or the box limit is `CONFLICT` (6); an unknown box or snapshot, a size that cannot hold the disk, and the snapshot limit are `CHECK_FAILED` (5). Every route, body, and refusal code is on the [API reference](https://prized.dev/docs/api#snapshots). --- # Environments > What a new box inherits, kept as versions: repos to clone, a setup script, env vars, secret files, and which of your credentials it may receive (CLI and API only). URL: https://prized.dev/docs/environments ## What a box receives [#what-a-box-receives] Pick an environment at create time (`--environment` on `prized box new`, `environment` on `POST /api/v1/boxes`) or let the default apply. The box receives, in order: | Item | When | Where it lands | | ------------ | ----------------- | -------------------------------------------------------------------------------------------- | | Env vars | first boot + live | `/etc/doppel/box-env.sh`, sourced by every login shell | | Repos | first boot | `~/` (default: the repo name), cloned as your box user, blocking repos first | | Setup script | first boot | Runs once as your box user in your home, after the repos; log in `/var/log/doppel-setup.log` | | Secret files | first boot + live | `~/`, owned by your box user, mode 0600 unless you set another | Env vars and secret files stay live: a change reaches the box within seconds, for new login shells. Secret files travel over the box's authenticated channel, never in the launch configuration, and the CLI shows only their path and size. ## The default environment [#the-default-environment] There is none until you mark one: `--default` when creating, or `prized environments default `. A box created from the CLI or API without naming an environment then takes it (`environment: null` on the API opts out). A box from the dashboard's [New box](https://prized.dev/docs/dashboard#new-box) page, the box your plan launches, a restore, and a fork never inherit one. ## Versions and upgrade [#versions-and-upgrade] Every save that changes what a box inherits mints a new immutable version (a rename or new description does not), and a box keeps its version until you upgrade it. Names are unique in the workspace regardless of case: `prod` and `Prod` are one name. ```text title="Versions" environment ml v1 ──── v2 ──── v3 (latest) │ │ box kiwi (created v1) ●────────┼──── upgrade ──▶ v3 box mango (created v2) ●──── stays on v2 until upgraded ``` `prized environments upgrade ` pins boxes to the latest version: | On upgrade | What happens | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Env vars and secret files | Re-apply within seconds; a dropped file or var is removed from the box. | | Repos and the setup script | Do not re-run. | | A safe-for-third-parties version | Makes the box restricted, permanently: mounted secrets stop resolving, its phone password is revoked, device keys leave. | | A box whose own vars would not fit the launch budget | Skipped (named in `skipped`); it keeps its version. | Up to 50 versions are kept per environment; older ones no live box pins are pruned. ## Safe for third parties [#safe-for-third-parties] A box for someone else (a contractor, a demo, an agent you do not fully trust) should hold none of your credentials: switch on safe for third parties on its environment (`prized environments set --safe-for-third-parties true`), or tick **Restricted** when creating one box. Four channels close: | Channel | What is refused | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Secret mounts | `POST /api/v1/boxes/:id/secrets` answers `409 box_restricted`; no [connector](https://prized.dev/docs/connectors) or secret can be mounted, and the box receives no [AWS profile](https://prized.dev/docs/aws) | | Device keys | No member device SSH keys reach the box; desktop apps that rely on them cannot log in | | Phone access | `POST /api/v1/boxes/:id/mobile-ssh` answers `409 box_restricted` | | CLI credential handoff | `prized agents handoff` and the agent, secrets and data steps of `prized setup` refuse with `CHECK_FAILED` (exit 5); `prized setup` also keeps `.env` files out of every sync session to the box | Env vars still apply. A safe environment cannot carry secret files (`safe_environment_has_secret_files`), and a restricted box never receives any, at create or on any upgrade, whatever environment it runs. Restricted is one-way: set at create (`restricted: true`, or a safe environment), by an upgrade onto a safe version, or inherited by a restore, and never unset. The three channels can also be closed one at a time on an environment without restricting the box. ## Repos [#repos] A repo is `owner/name` (GitHub) or an `https://` clone URL, with an optional branch, directory under your home (default: the repo name), and setup script run inside the clone. A **blocking** repo clones before the others and before the environment's setup script. Clones are plain https, so no credential lands in `.git/config`, and an existing directory is left alone. For a private repo, clone on the box once `gh` is signed in there ([Credentials](https://prized.dev/docs/credentials)), or ship a deploy key as a secret file and clone in the setup script. ## Vars and secret files [#vars-and-secret-files] Env vars are not secret: shell-identifier names, single-line values, shown in the CLI. Put anything sensitive in a [secret](https://prized.dev/docs/connectors) or a secret file: up to 64 KiB at a path relative to your home (`.aws/credentials`, `.npmrc`), encrypted at rest, decrypted only for its box, written 0600 or the mode you set, up to 16 per environment. ## Per-box vars [#per-box-vars] Set a box's own vars with `env` at create (`POST /api/v1/boxes` with `{"env": {"NAME": "value"}}`) or **Variables** on the box page's **Settings** tab (`PATCH /api/v1/boxes/:id` with `{"env": {...}}`, which replaces the set). A per-box var wins over an environment var of the same name. Environment plus per-box vars must fit the launch budget of about 16 KiB compressed; a create or PATCH that would not fit is refused with `environment_too_large`. ## Limits [#limits] Every cap, the launch budget included, is on [Limits](https://prized.dev/docs/limits#environments). ## CLI [#cli] `prized environments` (alias `prized envs`); every command takes `--json`. | Command | What it does | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `prized environments ls` | One row per environment. JSON: `{"environments": [...]}` | | `prized environments info ` | Channels, repos, vars, secret files (path, mode, size), versions with box counts. JSON: `{"environment": {...}}` | | `prized environments new [--description] [--setup-script FILE] [--default]` | Create; `--default` marks it the default for new boxes | | `prized environments default ` | Make it the default for new boxes | | `prized environments rm ` | Delete (asks; `-y` skips); boxes keep their version | | `prized environments set --safe-for-third-parties true\|false --secret-mounts true\|false --device-keys true\|false --phone-access true\|false [--description] [--setup-script FILE]` | Flip the switches or replace the description or script | | `prized environments set-var K=V` / `rm-var K` | Set or remove one env var | | `prized environments set-file --from FILE\|- [--mode 0600]` / `rm-file ` | Set a secret file from a local file or stdin, or remove it | | `prized environments add-repo owner/repo [--branch] [--dir] [--setup-script FILE] [--blocking]` / `rm-repo owner/repo` | Add, replace, or remove a repo | | `prized environments upgrade [--box B]...` | Pin every live box (or just `--box` ones) to the latest version. JSON: `{"environment", "version", "upgraded": [ids], "skipped": [ids]}` | The CLI's environment object carries the API row's fields in snake\_case (`safe_for_third_parties`, `secret_files`, `latest_version`, `versions` with `boxes` counts). Exit codes follow the [CLI table](https://prized.dev/docs/cli#for-agents-and-scripts): an unknown environment or a restricted-box refusal is `CHECK_FAILED` (5), a bad value `USAGE` (2), a taken name `CONFLICT` (6). ## API [#api] All routes take your session or a `dcp_` CLI bearer, are scoped to your workspace, and take JSON bodies. | Route | Body / answer | | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /api/v1/environments` | `{"environments": [row]}`; a row carries `isDefault`, the four switches, `latestVersionNo`, `repos`, `secretFiles` as `{path, mode, size}`, `versions` as `{versionNo, createdAt, boxCount}` | | `POST /api/v1/environments` | `{name, description?, baseTier?, setupScript?, envVars?, repos?, secretFiles?: [{path, content (base64), mode?}], safeForThirdParties?, passSecretMounts?, passDeviceKeys?, passPhoneAccess?, isDefault?}` → 201 `{environment}` | | `PATCH /api/v1/environments/:id` | Any of the above; a config field mints a version → 200 `{environment}` | | `DELETE /api/v1/environments/:id` | 204; boxes keep their version | | `POST /api/v1/environments/:id/default` | Make it the default → `{environment}` | | `POST /api/v1/environments/:id/toggles` | `{safeForThirdParties?, passSecretMounts?, passDeviceKeys?, passPhoneAccess?}` → `{environment}` | | `POST /api/v1/environments/:id/vars` | `{name, value}` → `{environment}`; `DELETE /:id/vars/:name` removes | | `PUT /api/v1/environments/:id/files` | `{path, content (base64), mode?}` → `{environment}`; `DELETE /:id/files?path=` removes | | `POST /api/v1/environments/:id/repos` | `{repo, branch?, dir?, setupScript?, blocking?}` → `{environment}`; `DELETE /:id/repos?repo=` removes | | `POST /api/v1/environments/:id/upgrade` | `{boxIds?}` → `{upgraded: [ids], skipped: [ids], latestVersionNo}` | On the box routes, `POST /api/v1/boxes` takes `environment` (id or name; `null` = none; absent = your default), `env: {K: V}` and `restricted`; `PATCH /api/v1/boxes/:id` with `{env}` replaces the per-box vars; `GET /api/v1/boxes/:id` carries `environment: {id, name, versionNo, latestVersionNo}`, `envVars` and `restricted`. Errors: `400 invalid_repo`, `invalid_file`, `invalid_env_var`, `invalid_env`, `too_many_repos`, `too_many_files`, `environment_too_large` (also from the box create and PATCH), `safe_environment_has_secret_files`; `404 not_found`; `409 name_taken`; `409 box_restricted` from the mount and phone-access routes. --- # Credentials > How your agents' and tools' sign-ins reach a box: handed over from your machine through the SSH tunnel, never seen, stored, or proxied by the control plane. URL: https://prized.dev/docs/credentials ## Hand them over from your machine [#hand-them-over-from-your-machine] ```bash prized setup # or, for the agents alone: prized agents handoff claude codex ``` Each asks first: `prized setup` once for the agents it found, `prized agents handoff` per agent with the exact files listed, and the Cursor installer (`--install-cursor`) separately. The GitHub token and git identity go along when `gh` is signed in on your machine (`--no-gh` skips them; `--non-interactive` runs and `prized agents handoff` need `--gh`). | Tool | What travels | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Claude Code | Your sign-in (the macOS Keychain item or `~/.claude/.credentials.json`), with your MCP servers and settings, to `~/.claude/.credentials.json` on the box (0600). The box refreshes it from then on, so one side will eventually ask you to `/login` again. With no readable local sign-in, nothing is copied and the box keeps its own, as the report says (on a box a teammate used, `/login` to make it yours); sign in on the box with `claude`, then `/login`, or locally and re-run the handoff to carry your settings too. | | Codex | `config.toml` (with your MCP servers) and an API-key sign-in in `~/.codex/auth.json`. A ChatGPT sign-in stays put, since its single-use refresh token would sign one side out: the handoff runs `codex login` on the box and opens its sign-in page in your browser; when the browser says the sign-in was delivered, return to your terminal for the result. With `--json` or `--non-interactive`, the report prints the steps instead: `prized ssh`, then `codex login --device-auth` on the box. | | Cursor | MCP and CLI config. Cursor's sign-in lives in your system keychain, which Prized never reads; on the box, `agent login` prints a URL to open. | | GitHub | Your `gh` token, piped to `gh auth login --with-token` (never on a command line), plus your git name and email if the box has none. | | Other CLIs | With `--env-secrets` (or a yes to the secrets question): the project's `.env` files, CLI sign-in files (`~/.aws`, `~/.config/gcloud`, `~/.kube/config`, ...), and secret-looking env vars from your shell files, written 0600 ([Bring your environment](https://prized.dev/docs/environment)). For AWS, a [connected role](https://prized.dev/docs/aws) needs none of this. | A box [safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties), itself or through its environment, never receives your credentials: the handoff and setup's agent, secrets and data steps refuse it with `CHECK_FAILED` (exit 5). They also copy nothing while the box's status cannot be checked: signed out is `AUTH` (exit 8, run `prized login`); offline, or a box outside your workspace, is `CHECK_FAILED` (exit 5). `prized setup` still syncs the project and runs its other steps. MCP servers whose sign-in cannot travel are reported as needing re-auth, with the command to run. The handoff is a one-time copy: re-run it after changing MCP config. ### What the report means [#what-the-report-means] | Result | Meaning | | -------------- | --------------------------------------------------------------------------------------- | | `ok` | The tool says it is signed in (for GitHub, as the same account as your machine). | | `needs-reauth` | The files landed, but the check disagreed, could not run, or found a different account. | | `manual` | Nothing to transfer; the printed steps sign you in on the box. | | `needs-rerun` | Skipped on purpose (the agent was running); re-run the handoff afterwards. | ### Symlinks and order [#symlinks-and-order] The handoff never writes a credential through a symlink, which on a box with a synced folder could land it in your repo: a symlinked directory (`~/.codex`, `~/.cursor`, `~/.claude`, or a parent) stops the transfer and names the path, and a symlinked file is replaced by a real file. Installs run first; the GitHub token goes last. From your machine, only real files travel: a symlinked config file (`~/.codex/auth.json` pointing elsewhere) is left out, and the consent prompt and the report say so. ## Or sign in on the box [#or-sign-in-on-the-box] ```bash prized ssh gh auth login claude codex login --device-auth ``` Once per box is enough; disks persist. `codex login --device-auth` prints a code to enter in a browser on any device; if your account has no device-code sign-in, run `prized agents handoff codex` on your machine to sign the box in through your browser. ### Copying the sign-in link [#copying-the-sign-in-link] Claude prints a sign-in link: select it and press **c** to copy it to your clipboard through `prized ssh` ([Copy out](https://prized.dev/docs/sessions#copy-out)). Paste it in a browser, then paste the code back into Claude. ## Secrets for agents [#secrets-for-agents] For API keys an agent should use but never read, use **Secrets** in the dashboard: a value is encrypted at rest, cannot be read back, and shows up on the box only as a placeholder. The box's broker swaps in the real value on requests to HTTPS hosts you allowlisted; plain HTTP is refused. A secret is open to every box but a contractor's until you grant it to boxes or members; scoping and each secret's usage record (never a value) are on [Teams: Scoped secrets](https://prized.dev/docs/teams#scoped-secrets). Provider presets are [Connectors](https://prized.dev/docs/connectors). ## Keys for other agents and backends [#keys-for-other-agents-and-backends] OpenCode and pi have no handoff, and a model written `amazon-bedrock/…`, `openrouter/…` or `deepseek/…` runs any agent on that backend ([Models and backends](https://prized.dev/docs/remote-agents#models-and-backends)). Both read keys from the box's environment: add each key as a Secret under the name below, with its allowed host, and plain values such as `AWS_REGION` as an [environment](https://prized.dev/docs/environments) or per-box env var. | Backend | Set on the box | Allowed host for the Secret | | -------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | Amazon Bedrock | `AWS_REGION`, plus `AWS_BEARER_TOKEN_BEDROCK` (a Bedrock API key), or `AWS_PROFILE` naming a [connected AWS role](https://prized.dev/docs/aws) | `bedrock-runtime..amazonaws.com`, and `bedrock-mantle..api.aws` for Codex | | OpenRouter | `OPENROUTER_API_KEY` | `openrouter.ai` | | DeepSeek | `DEEPSEEK_API_KEY` | `api.deepseek.com` | What each agent gets on a backend, set for that run only (its config files are never edited): | Agent | Amazon Bedrock | OpenRouter | DeepSeek | | ------------ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | Claude Code | `CLAUDE_CODE_USE_BEDROCK=1`; region from `AWS_REGION` or the profile | `ANTHROPIC_BASE_URL=https://openrouter.ai/api`, `ANTHROPIC_AUTH_TOKEN` set to the key | `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic`, `ANTHROPIC_AUTH_TOKEN` set to the key | | Codex | Its built-in `amazon-bedrock` provider, in a region Bedrock serves OpenAI models from | A provider for `https://openrouter.ai/api/v1` reading `OPENROUTER_API_KEY` | A provider for `https://api.deepseek.com/` reading `DEEPSEEK_API_KEY`, web search off | | OpenCode, pi | Read the same variables themselves | Read `OPENROUTER_API_KEY` | Read `DEEPSEEK_API_KEY` | On OpenRouter and DeepSeek, Claude Code also gets `ANTHROPIC_API_KEY` and `CLAUDE_CODE_OAUTH_TOKEN` blanked, over your settings files too, so your Claude sign-in is never sent to another provider. The run refuses to start without the backend key, or when a Claude Code settings file (yours, wherever `CLAUDE_CONFIG_DIR` puts it, or the project's) sets its own `ANTHROPIC_AUTH_TOKEN` or `apiKeyHelper`. > AWS access keys sign requests instead of travelling in them, so a Secret placeholder cannot stand in for them: use a Bedrock API key, a connected role, or a `~/.aws/credentials` secret file. Without a backend, OpenCode and pi use the provider their model names, with that provider's usual key: `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` (the Anthropic and OpenAI [Connectors](https://prized.dev/docs/connectors) set them), `GEMINI_API_KEY`, `MISTRAL_API_KEY` and the rest. Or sign in on the box once: `opencode auth login`, or `pi` then `/login`. ## The machine itself [#the-machine-itself] Every box is its own machine, reached only through your encrypted tunnel; what it exposes and what the control plane can see are on [Security and trust](https://prized.dev/docs/security). ## What Prized itself stores [#what-prized-itself-stores] `prized login` writes an API key and an SSH identity to `~/.config/prized` on your machine, and registers the identity's public key with your workspace as a restricted, tunnel-only fallback for SSH clients that cannot use certificates ([Claude Desktop remote](https://prized.dev/docs/claude-desktop)). The private key never leaves your machine, and your dev credentials never leave your machines. Revoking a machine's API key signs it out and drops its SSH key from your boxes ([Workspaces: API keys](https://prized.dev/docs/workspaces#api-keys)). While signed in, the CLI reports which commands ran (name, version, OS, duration, exit code), never arguments, paths, or file contents; opt out under [CLI: usage telemetry](https://prized.dev/docs/cli#usage-telemetry). --- # Connectors > Mount brokered credentials for ClickHouse, OpenAI, Anthropic, GitHub, or Slack into a dev box without writing the real value to its disk. URL: https://prized.dev/docs/connectors ## Connect a service [#connect-a-service] In the dashboard's **Connectors**, choose a provider and a box, then paste the credential. It becomes an encrypted workspace [secret](https://prized.dev/docs/credentials#secrets-for-agents), which a [contractor](https://prized.dev/docs/teams#roles)'s role does not include: the box sees a placeholder under the env name below, swapped for the real value on HTTPS requests to the allowed host (plain HTTP is refused). | Connector | Default env name | Allowed host | | ---------- | --------------------- | ------------------------------ | | ClickHouse | `CLICKHOUSE_PASSWORD` | The HTTPS endpoint you enter | | OpenAI | `OPENAI_API_KEY` | `api.openai.com` | | Anthropic | `ANTHROPIC_API_KEY` | `api.anthropic.com` | | GitHub | `GITHUB_TOKEN` | `api.github.com`, `github.com` | | Slack | `SLACK_BOT_TOKEN` | `slack.com` | ## ClickHouse [#clickhouse] Enter the HTTPS endpoint (for example `https://abc.clickhouse.cloud:8443`) and the password. Only the password is mounted, on the box you picked alone (widen it under **Dashboard → Secrets**); the endpoint, username, and database belong in your project's non-secret config. ```bash curl -sS "$CLICKHOUSE_URL/?database=$CLICKHOUSE_DATABASE" -H "X-ClickHouse-User: $CLICKHOUSE_USER" -H "X-ClickHouse-Key: $CLICKHOUSE_PASSWORD" --data-binary 'SELECT 1' ``` ## Client compatibility [#client-compatibility] The client must send the env value unchanged in an HTTPS request header, as most bearer-token SDKs do. A client that transforms it first (into Basic-auth base64, say) gets no substitution: send a raw token header, or store the transformed value as the credential if the upstream accepts it. ## Rotate or remove [#rotate-or-remove] Rotate or delete the credential under **Secrets**; the box's **Settings** tab, under **Secrets**, shows and removes its env binding. --- # AWS access > Connect an IAM role once, and every box in your workspace gets an AWS profile that assumes it with a short-lived identity token instead of access keys. URL: https://prized.dev/docs/aws ## How it works [#how-it-works] Prized is an OpenID Connect identity provider for your boxes: the AWS CLI and SDKs exchange a box's signed token for temporary credentials with `AssumeRoleWithWebIdentity`, against a role your administrator controls. ```bash title="you@box ~" aws sts get-caller-identity --profile acme # { # "Arn": "arn:aws:sts::123456789012:assumed-role/prized-devbox/you@acme.com" # } ``` Nothing to install or sign in to: each box's `~/.aws/config` gets a `[profile ]` per connected role, read by `aws`, `terraform`, every SDK, and `kubectl` after `aws eks update-kubeconfig`. Credentials last an hour; the box renews its token itself, so a box running for a month still works. ## Connect an AWS account [#connect-an-aws-account] In **Dashboard → Settings → General**, under **Cloud federation** (owners only): 1. Click **Connect** on the Amazon Web Services row; the AWS console opens a pre-filled CloudFormation stack. 2. In the AWS account, review the role name and policies, tick the IAM acknowledgement, and click **Create stack**. 3. The role shows **Connected** only after AWS confirms that your workspace can assume it; until then, boxes get no AWS profile. If someone else administers AWS, **Copy** the link and send it to them: it works once, for one hour, without a Prized account. The stack creates the identity provider, a `prized-devbox` role with `ReadOnlyAccess` (both changeable before you create it), and a one-shot function that reports the role to Prized. A second workspace in the same AWS account sets **CreateIdentityProvider** to `No` (one provider per URL per account). ## Connect a role you created yourself [#connect-a-role-you-created-yourself] To skip the stack: 1. Create the identity provider: URL `https://prized.dev`, audience `sts.amazonaws.com`. 2. Create a role whose trust policy allows `sts:AssumeRoleWithWebIdentity` from it, with `prized.dev:aud` = `sts.amazonaws.com` and `prized.dev:sub` like `workspace::*`. 3. Connect the role's ARN with the [API](https://prized.dev/docs/api#cloud-federation). Prized checks the role with AWS at once; the row shows **Connected**, or **Check failed** with what to fix. **Verify** rechecks; **Remove** ends it for every box. A workspace can connect up to 10 roles, each its own profile. Members can see but not change them; a contractor's role does not include cloud federation. ## What AWS sees [#what-aws-sees] Each assumption carries the box's subject, `workspace::box:`, and the box owner's email as the role session name, so CloudTrail shows `assumed-role/prized-devbox/you@acme.com` per person. Permissions are the role's; to pin a role to one box, put that box's id in the trust policy's `sub` condition. ## On the box [#on-the-box] | What | Where | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | The profiles | A fenced block in `~/.aws/config` that Prized keeps current; everything outside it, including profiles `prized setup` copied from your laptop, is yours | | The identity token | `~/.prized/aws-token`, readable by your user only, renewed by the box | | Credentials | Minted per call by the CLI or SDK, cached as usual (`~/.aws/cli/cache`) | Removing a role or closing the workspace clears its profile and token from every box within a few minutes. Boxes [safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties) (every contractor's box among them) and boxes from an environment with **Secret mounts** off never get either, as with workspace secrets. Deleting a box stops it from receiving new AWS identity tokens; so does removing a member or that member leaving the workspace, for all their boxes, whose managed profiles and token go on the next refresh. Tokens already issued remain valid until they expire, about an hour; temporary AWS credentials already obtained have their own expiry. To end existing AWS sessions sooner, revoke them in AWS. ## Troubleshooting [#troubleshooting] | Symptom | Cause and fix | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | The stack fails with `Prized refused the connect (connect_link_expired)` or `(connect_link_cancelled)` | The link is over an hour old, or was cancelled or replaced. Make a new link under **Cloud federation** and create the stack again. | | The stack fails with `Prized could not verify the role` | AWS has not accepted the role or its trust policy. No role was connected. Fix what the stack events name, then create the stack again from a fresh link. | | `Check failed: Account … has no identity provider for https://prized.dev` | The provider is missing or has another URL. Create it with the exact URL and audience above. | | `Check failed: AWS refused the role: its trust policy does not admit this workspace` | The `sub` condition or the audience does not match. Fix the role's trust policy, or delete the stack and create it again from a fresh link. | | `Unable to locate credentials` on the box | No profile is selected: pass `--profile ` or `export AWS_PROFILE=`. | | The profile works on one box and not another | Wait a few minutes for the other box to pick it up. A box safe for third parties never gets it. If `~/.aws` or `~/.aws/config` there is a symlink (dotfiles, a synced folder), Prized writes nothing; move the real file into place. | | `RegionDisabled` | The global STS endpoint is off in your account. Enable it in IAM account settings, or set `sts_regional_endpoints = regional` and a `region` on the profile. | --- # Sandboxes > Sandboxes (beta): Linux microVMs your code creates through the API or the CLI. Each boots from a template in seconds, runs commands and moves files, pauses for the price of its storage, and forks into many live copies. URL: https://prized.dev/docs/sandboxes Each sandbox is its own Firecracker microVM, with its own kernel. Sandboxes live in your workspace next to your boxes, draw on the same balance, and answer to the same API keys. ## Sandboxes and boxes [#sandboxes-and-boxes] | | Box | Sandbox | | ----------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | For | You, working on it every day | Your code and agents, one job at a time | | Made by | The dashboard or `prized box new` | The [API](https://prized.dev/docs/sandboxes-api), the [CLI](https://prized.dev/docs/cli#sandboxes), or the dashboard | | Lives | Until you delete it | Until you delete it, or 24 hours at most | | Starts | Under a minute | Seconds | | You reach it with | `prized ssh`, sync, ports, the browser terminal | Commands and file copies through the API, or `prized sandbox exec` and `cp` | | User | Your login user, with sudo | `prized`, without sudo | | Billed | By size, hourly from a monthly price ([Billing](https://prized.dev/docs/billing)) | Per second, by vCPU, memory, and disk ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)) | * **Use a box** for the machine you work on: your repo, editor, synced folders, dev servers, and agents that run for days. * **Use a sandbox** to run code you did not write or have not read (an agent's tool call, a user's snippet), one clean machine per request or test run, the same job fanned out across many copies, or anything that should be gone afterwards. Two guides walk through it: [Run code from an AI agent in an isolated VM](https://prized.dev/docs/sandboxes-run-agent-code) and [Fork a running VM to run work in parallel](https://prized.dev/docs/sandboxes-fork-parallel). ## Quickstart [#quickstart] A first sandbox in about a minute: 1. [Open Sandboxes](https://prized.dev/onboarding/sandboxes). New to Prized? It signs you up with the $30 [free credit](https://prized.dev/docs/billing#free-credit), no card, and opens the dashboard on Sandboxes, with no box tour. 2. Click **Create API key** there and export the key, as below. It reaches sandboxes only and expires after 90 days; the button shows until the workspace has a sandbox. A key from **Dashboard → Settings → API keys** with **Full access**, or **Custom** with **Sandboxes: Write**, works too. 3. Run one tab below. Each creates a sandbox from `prized-base-v1`, runs one command, and deletes it. The curl tab needs `jq`, and retries its command, which only prints a version, since a new sandbox can answer `409` or `502` for a moment after `running` ([The edge](https://prized.dev/docs/sandboxes-api#the-edge)). The CLI tab needs `prized` v0.21.0 or later: run `curl -fsSL https://prized.dev/install.sh | sh`, then the reload line it prints, in the terminal where you exported the key ([Windows](https://prized.dev/docs/quickstart#2-install-the-cli)). ```bash export PRIZED_API_KEY=dcp_… ``` On the free credit, sandboxes run within the [free tier](#free-tier); once the workspace has paid in, the full quotas apply ([Who can use sandboxes](#who-can-use-sandboxes)). **curl** ```bash API=https://api.prized.dev/api/v1 AUTH="Authorization: Bearer $PRIZED_API_KEY" CREATED=$(curl -s -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" -d '{"template":"prized-base-v1"}') SBX=$(echo "$CREATED" | jq -r '.sandbox.id // empty'); echo "${SBX:-$CREATED}" # sbx_3f9c0a1b2d4e5f60, or the refusal, such as {"error":"insufficient_balance"} for i in $(seq 60); do STATE=$(curl -s "$API/sandboxes/$SBX" -H "$AUTH" | jq -r .sandbox.state); [ "$STATE" = starting ] || break; sleep 1; done; echo "$STATE" # running TOOLBOX=$(curl -s "$API/sandboxes/$SBX" -H "$AUTH" | jq -r .sandbox.toolboxBase) curl -s --retry 5 --retry-all-errors --fail-with-body -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"python3 --version"}' # {"stdout":"Python 3.12.3\n","stderr":"","exitCode":0,"durationMs":41} curl -s -X DELETE "$API/sandboxes/$SBX" -H "$AUTH" > /dev/null ``` **CLI** ```bash ID=$(prized sandbox create) prized sandbox exec $ID -- python3 --version # Python 3.12.3 prized sandbox rm $ID ``` A create on warm capacity is usually running in about five seconds; at busy moments it can wait in `starting` while capacity is added. Every field of a create is on [Sandbox API: Create](https://prized.dev/docs/sandboxes-api#create). ## States [#states] | State | Meaning | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `starting` | Booting from its template, or coming back from a pause. | | `running` | Up: commands and files work, and it bills at the running rate. | | `pausing` | Saving its memory and disk. | | `paused` | Memory and disk saved, no compute billed. A command or file request resumes it ([Pause and resume](#pause-and-resume)). | | `resizing` | Changing shape while paused; it returns to `paused`, or goes to `error` if the resize fails partway. | | `deleting` | Being torn down. | | `deleted` | Gone: reads answer `404 not_found`. | | `error` | Never came up, or lost the machine it ran on; `errorReason` says why. It costs nothing and is removed after `autoDeleteMin`, or after an hour. Delete it and create another. | ## Pause and resume [#pause-and-resume] A paused sandbox keeps its memory as well as its disk: on resume, every process carries on where it stopped. A single resume is usually back to `running` within two seconds. Pause and resume answer `202` at once; read the sandbox until it reaches `paused` or `running`, or send them with `?wait=` and the answer comes once it has, if that is within about 8 seconds (the hold, plus up to half a second for its last look); past that it answers `202` as before, still `pausing` or `starting`, and you read it until it settles ([Sandbox API: Waiting](https://prized.dev/docs/sandboxes-api#waiting)). The SDK waits that way for you, and so do the CLI's `create` and `resume`; `prized sandbox pause` answers at once unless you add `--wait`. * **Paused costs storage only.** Disk and saved memory bill at the storage rate, no compute ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)). * **Paused still holds its quota.** A paused sandbox keeps its CPU and memory reserved for its resume, so it counts against the workspace's running CPU and memory until you delete it ([Limits: Sandboxes](https://prized.dev/docs/limits#sandboxes)). * **Paused state lives on one machine.** A paused sandbox's memory and disk stay on the machine it ran on. If that machine fails, or shortly after a pause the sandbox's process there stops unexpectedly, the sandbox goes to `error` and what it held is lost. Keep anything you need outside the sandbox. * **A command wakes it.** A command, upload, or download sent to a paused sandbox resumes it, waits for it, and then runs; one sent while a sandbox is still starting or resuming waits the same way. The wait is up to 10 seconds, shared by the requests with the same key that arrive during it ([The edge](https://prized.dev/docs/sandboxes-api#the-edge)); past that the request answers `503 sandbox_not_ready` with nothing run, and a retry picks it up. A sandbox that is `resizing` is not woken: its requests answer `409 sandbox_not_running` until the resize ends. It is an ordinary resume: billed at the running rate from then on, counted as activity, and refused exactly as `POST /resume` would be, with the same error (`409 insufficient_balance`, `429 sandbox_quota_exceeded`, and the rest). * **To never wake one,** send the header `Prized-Auto-Resume: false` on the request: a paused sandbox then answers `409 sandbox_not_running` and stays paused. The header only stops the wake: on a running sandbox the request runs and counts as activity like any other, so it holds off automatic pause. To watch a sandbox without keeping it running, read `GET /api/v1/sandboxes/{id}` instead, which is not activity. ## Automatic pause and delete [#automatic-pause-and-delete] Three create fields bound a sandbox's life, so a forgotten one stops costing money: | Field | Default | Effect | | --------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `autoPauseMin` | Off | Pause after this many minutes with no activity. | | `autoDeleteMin` | Off | Delete after this many minutes paused. | | `maxRuntimeMin` | 1,440 (24 hours); 60 on the [free tier](#free-tier) | Delete this many minutes after the create, whatever its state. Up to 1,440, or 60 on the free tier. | Exec and file requests count as activity, and a command keeps its sandbox awake for as long as it runs. `POST /api/v1/sandboxes/{id}/refresh` counts too, for an idle stretch with nothing to run. A resumed sandbox starts its idle count again. > [!WARNING] > Deleting a sandbox, by hand or by `autoDeleteMin` or `maxRuntimeMin`, erases its disk and memory, with no undo. Copy out what you need first. ## Fork [#fork] ```bash curl -s -X POST "$API/sandboxes/$SBX/fork" -H "$AUTH" -H "Content-Type: application/json" -d '{"count":3}' ``` A fork turns one running sandbox into `count` live copies: each child has the parent's memory, disk, and every running process, as they were at the fork. The parent pauses briefly, usually under a second (up to a few seconds on its first fork), then keeps running. Children are ordinary sandboxes with their own ids, billed and counted like any other. * **The parent must be `running`.** Fork it before you pause it. * **Copies start on the parent's machine.** A request takes up to 256, but one machine holds at most 31 copies of a 1 vCPU sandbox, 15 of a 2 vCPU one, 7 of a 4 vCPU one (the default shape), and 3 of an 8 vCPU one; more memory or disk, or a busy machine, fits fewer. New sandboxes start, when they can, on a machine with room to fork them. * **A fork that does not fit makes nothing.** It answers `409 fork_capacity` with `fits`, how many copies fit now: fork that many or fewer (when `fits` is above 0), try again later, or start a new sandbox and fork that (it starts fresh). Keep the parent until the new one is `running`: while it holds its place, the new one cannot take that place on a full machine. The new sandbox and each copy count toward your [quota](https://prized.dev/docs/limits#sandboxes) (the [free tier](#free-tier) allows 4 at once): at the limit, delete sandboxes you no longer need and wait until they are gone (`GET /api/v1/sandboxes/{id}` answers `404`, or `prized sandbox ls` no longer lists them; a sandbox still `deleting` counts), or raise it: on the free tier by topping up, otherwise by emailing the support address shown on https://prized.dev/docs/troubleshooting. Copy out what you need before you delete one, and never delete a parent while a copy forked from it is still `starting`: that copy fails. * **A sandbox too big to copy cannot fork at all.** When its machine cannot hold even one copy beside it, a fork answers `409 parent_not_forkable` with `reason` `parent_too_big`, the `field` that does not fit (`vcpu`, `memoryMb`, or `diskGb`), and a `message`. That starts past about 26 GB of memory, or about 105 GB for a sandbox created [large](#large-sandboxes). No retry or smaller `count` helps; fork a smaller sandbox. * **Quota counts every child.** A fork that would pass a quota answers `429 sandbox_quota_exceeded` with `admitted`, how many would have fit, and makes none. * **Children inherit** the template, shape, [environment variables](#environment-variables), labels (unless the fork names its own), `autoPauseMin`, `autoDeleteMin`, and the parent's deletion time: a child never outlives its parent's `maxRuntimeMin`. ## Resize [#resize] A paused sandbox can change its vCPU, memory, and disk within its template's bounds; disk only grows. The next resume boots fresh from the resized disk, so the paused memory and processes are discarded. A resize needs room in your quota for the larger of the old and new shape until it completes ([Sandbox API: Resize](https://prized.dev/docs/sandboxes-api#resize)). ## Templates [#templates] | Template | What is in it | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `prized-base-v1` | Ubuntu 24.04, build-essential, git, curl, jq, ripgrep, unzip, Python 3 with pip and venv, Node.js 22 with npm, corepack, and pnpm | | `prized-agent-v1` | Everything in `prized-base-v1`, plus Claude Code and Codex | * **Shape.** Both default to 4 vCPU, 8 GB of memory, and a 10 GB disk, and take 1 to 32 vCPU, 1 to 128 GB of memory, and 10 to 128 GB of disk (2 vCPU and 4 GB at most on the [free tier](#free-tier)). Up to 16 vCPU and about 58 GB of memory a sandbox is standard; past either it is [large](#large-sandboxes). `GET /api/v1/sandbox-templates` lists each template with its defaults and bounds. * **A template keeps its tools.** Its tools are pinned; newer versions arrive as a new template name (`prized-agent-v2`), so a sandbox made today has the same tools as one made next month. * **A name can get a fixed build.** A fix to the image that leaves the tools alone (boot speed, larger shapes) ships as a new build under the same name. New sandboxes start from the new build; a running or paused sandbox keeps the build it started from, and so do its forks. `templateBuild` on the sandbox, and `build` in `GET /api/v1/sandbox-templates`, name the build. * **Agents bring their own sign-in.** Nothing is signed in on `prized-agent-v1`: pass the key the CLI reads, such as `ANTHROPIC_API_KEY` for Claude Code, in `envVars`. ## Large sandboxes [#large-sandboxes] A sandbox on `prized-base-v1` or `prized-agent-v1` goes up to 32 vCPU, 128 GB of memory, and 128 GB of disk. A large sandbox is one with more than 16 vCPU or about 58 GB of memory; anything smaller is standard. * **The first create can take a minute or two.** It answers `201` with `state: "starting"` as usual and stays there until the sandbox is up; poll until `running`. * **The rate does not change.** It is billed per second at the same [rates](https://prized.dev/docs/billing#sandboxes) as any shape: the vCPU and memory of 32 vCPU with 128 GB cost 32 times those of a 1 vCPU, 4 GB sandbox, and disk is billed on top by its size. * **It counts against the same [quotas](https://prized.dev/docs/limits#sandboxes).** The defaults hold one 32 vCPU, 128 GB sandbox; two need a higher running-memory quota (email the support address shown on https://prized.dev/docs/troubleshooting). * **A resize can be refused for room.** A sandbox that cannot grow where it runs answers `409 resize_rejected` with `reason` `host_capacity`; create a new one at the larger shape instead. * **Forks** of a large sandbox are limited by its machine like any other ([Fork](#fork)). * **An older sandbox keeps its build's bounds.** The larger shapes came with a new build of each template. A sandbox created before it (its `templateBuild` is `prized-base-v1` or `prized-agent-v1` itself), and its forks, resize within 8 vCPU, 32 GB of memory, and 100 GB of disk; create a new sandbox to go larger. ## Inside a sandbox [#inside-a-sandbox] | | | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | User | `prized`, without sudo | | Home | `/home/prized` | | Working directory | `/home/prized/work`, where commands run unless they name another `cwd` | | Shell | Commands run under `bash -c` | | Files reachable by the API | Anything under `/home/prized`; symlinks are refused | | Network | Outbound internet. Nothing reaches a sandbox from the internet, and sandboxes cannot reach each other | | Installing more | No root, so no `apt`. `pip` installs into a venv, and `npm install -g` needs a user prefix (`npm config set prefix ~/.local`) | ## Environment variables [#environment-variables] `envVars` on the create are set for every command the sandbox runs, and for every command in its forks; a command's own `env` overrides them name by name. Up to 32, each name a shell identifier (letters, digits, and `_`, not starting with a digit) of up to 64 characters, each value up to 16 KiB. They are stored encrypted, never returned by any route, and erased with the sandbox, so `envVars` is the place for a key the sandbox's code needs. ## Who can use sandboxes [#who-can-use-sandboxes] * **Owners and members** of a workspace, and API keys they made. [Contractors](https://prized.dev/docs/teams#roles) do not see sandboxes; their requests answer `404 not_found`. * **Any balance above zero.** A workspace on the [free credit](https://prized.dev/docs/billing#free-credit) alone uses sandboxes within the [free tier](#free-tier); one that has paid in (a plan or a [top-up](https://prized.dev/docs/billing#topping-up)) gets the full [quotas](https://prized.dev/docs/limits#sandboxes). An empty balance cannot start one (`409 insufficient_balance`). * **API keys.** A key with **Full access** reaches sandboxes. A **Custom** key needs **Sandboxes: Read** to list them and **Sandboxes: Write** for everything else, running commands and moving files included. The key an agent got at [agent sign-in](https://prized.dev/docs/agent-sign-in) has Sandboxes: Write, and each sandbox it creates is a row on the [audit log](https://prized.dev/docs/teams#what-is-recorded). A key limited to some boxes reaches no sandboxes ([What a key can do](https://prized.dev/docs/workspaces#what-a-key-can-do)). ## Free tier [#free-tier] A workspace whose balance is only free credit (it has not paid in with a plan or a top-up) runs sandboxes within these limits: | Free tier | Limit | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | Sandboxes at once | 4, counted like the [sandbox quota](https://prized.dev/docs/limits#sandboxes): paused ones and ones in `error` count | | Running vCPU and memory, across the workspace | 8 vCPU and 16 GB | | Per sandbox | Up to 2 vCPU and 4 GB of memory. With no shape asked for, the template's default capped at 2 vCPU and 4 GB | | Lifetime | 60 minutes: `maxRuntimeMin` defaults to 60, and a larger value is refused | | Disk | 40 GB across the workspace's sandboxes | * **It spends the free credit.** Sandboxes draw on it at the [sandbox rates](https://prized.dev/docs/billing#sandboxes), like any compute; an empty balance answers `409 insufficient_balance`. * **Past a limit** the API answers as for any quota (`429 sandbox_quota_exceeded` with its `reason`, or `422 invalid_resources`), with `"tier": "free"` in the body and the limit it hit as `limit`: sandboxes for `sandbox_limit`, vCPU for `running_vcpu` and `vcpu`, MB for `running_memory` and `memoryMb`, GB for `retained_disk`, minutes for `max_runtime`. For example, `{"error": "sandbox_quota_exceeded", "reason": "running_memory", "tier": "free", "limit": 16384}`. A [top-up](https://prized.dev/docs/billing#topping-up) or a plan lifts the workspace to the full quotas. * **Sandboxes made while paying** resume on the free tier only if they fit it: one bigger than 2 vCPU or 4 GB answers `422 invalid_resources` (resize it down first), and one with more than an hour left before it is deleted answers `429 sandbox_quota_exceeded` with `reason` `max_runtime`. Its lifetime is never shortened. * **A resize** stays within 2 vCPU and 4 GB, and is accepted when the resized sandbox fits the free tier's totals, so shrinking one brings a workspace back inside them. * **The free credit's rules hold.** One process at full CPU for 30 minutes pauses the workspace's boxes and sandboxes ([Free credit](https://prized.dev/docs/billing#free-credit)). * **Switched off.** If free-tier access is switched off for your workspace, a create, resume, or fork on the free credit alone answers `409 paid_balance_required` until the workspace has paid in. ## Dashboard [#dashboard] **Sandboxes** in the dashboard's sidebar lists every sandbox in the workspace with its template, shape, state, and what it costs an hour, against the workspace's quotas. Create, pause, resume, and delete from there; before the first sandbox, it offers a key in one click and the first request ([Quickstart](#quickstart)). A key made there stays at the top of the list after your first sandbox appears, until **Done** or you leave the page. With the CLI, skip the installer's sign-in and setup steps (they are for boxes): the key is all `prized sandbox` needs. A sandbox's own page shows its countdowns to auto-pause and deletion, live CPU and memory, its processes, and its events. The **Templates** tab lists the catalog, and **API docs** opens the [Sandbox API](https://prized.dev/docs/sandboxes-api) reference. ## Not yet [#not-yet] | Not yet | Instead | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | | Templates from your own Dockerfile or image, or saving a sandbox as a template | Install what you need at the start of each run, or once in a sandbox you then [fork](#fork) | | Forking a box into sandboxes | Fork a sandbox | | Egress allow and deny lists | Every sandbox has outbound internet | | Sandboxes that outlive 24 hours | A [box](https://prized.dev/docs/boxes) | | More than 32 vCPU or 128 GB of memory | | | More than 128 GB of disk | A bigger [box](https://prized.dev/docs/machines#sizes) | | GPUs | | | Regions other than `us-west-2` (Oregon) | | | SSH or a terminal into a sandbox, or a preview link to one of its ports | Commands and file copies through the API, or `prized sandbox exec` and `cp` | | Streaming output | Write output to a file and read it with a later exec or a download | | TypeScript and Python SDKs | The [API](https://prized.dev/docs/sandboxes-api) with any HTTP client, or the CLI | | An MCP server | The [API](https://prized.dev/docs/sandboxes-api) or the CLI | --- # Sandbox API > Every sandbox route: create, list, pause, resume, fork, resize, and delete on the control plane; run commands and move files on the edge; with the fields, states, and error codes. URL: https://prized.dev/docs/sandboxes-api What a sandbox is, and how its lifecycle works, is on [Sandboxes](https://prized.dev/docs/sandboxes). The routes for boxes and the workspace are on [API reference](https://prized.dev/docs/api). ## Two hosts, one key [#two-hosts-one-key] | What | Control plane | Edge | | -------- | ------------------------------------------------------------------- | --------------------------------------------------------- | | Base URL | `https://api.prized.dev/api/v1` | The sandbox's `toolboxBase` | | Routes | `/sandboxes`, `/sandbox-templates` | `/process/execute`, `/files/upload`, `/files/download` | | Errors | `{"error": "code"}`, sometimes with `reason`, `field`, or `message` | `{"error": "code"}`, sometimes with `detail` or `message` | Read `toolboxBase` from the sandbox object rather than building it; it is the same for the sandbox's whole life. ```bash export PRIZED_API_KEY=dcp_… API=https://api.prized.dev/api/v1 AUTH="Authorization: Bearer $PRIZED_API_KEY" ``` ## Authentication [#authentication] Send `Authorization: Bearer dcp_…` on every request, with an API key from **Dashboard → Settings → API keys** ([API keys](https://prized.dev/docs/workspaces#api-keys)). Expiry, revocation, and rotation work as on the rest of the API ([Authentication](https://prized.dev/docs/api#authentication)). | Key | Reaches | | ---------------------------------------------------------------------------- | ---------------------------------------------- | | Full access | Every route below | | Read only, or Custom with **Sandboxes: Read** | `GET` routes: templates, the list, one sandbox | | Custom, **Sandboxes: Write**, or an [agent sign-in](https://prized.dev/docs/agent-sign-in) key | Every route below, the edge included | | Limited to some boxes | None: `403 key_access_denied` | A key acts as the member who made it: an owner's or member's key sees every sandbox in the workspace, and a [contractor](https://prized.dev/docs/teams#roles)'s gets `404 not_found` on every sandbox route. On the free credit alone, a workspace works within the [free tier](https://prized.dev/docs/sandboxes#free-tier)'s limits. ## Conventions [#conventions] | Topic | Rule | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | JSON | Send `Content-Type: application/json` on the control plane. A field the route does not know is `422 unknown_field` with `field`, never ignored. | | Ids | Sandboxes are `sbx_…`, forks `frk_…`. An unknown, malformed, deleted, or foreign id is `404 not_found`. | | 202 means in progress | Pause, resume, fork, resize, and delete answer `202` with the sandbox as written; its `state` moves within seconds. Poll `GET /sandboxes/{id}`, or have the API wait for you ([Waiting](#waiting)). | | Idempotency | `POST /sandboxes` and `POST /sandboxes/{id}/fork` take an `Idempotency-Key` header (1 to 128 printable characters): a retry with the same key and body answers `200` with the first result, and makes nothing. The same key with another body is `409 idempotency_conflict`. | | Units | `memoryMb` is MiB, `diskGb` is GiB, timers are whole minutes, timestamps are RFC 3339 UTC. | | Stability | Fields are added without notice; ignore the ones you do not know. | ## Templates [#templates] | Route | Answer | | ------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `GET /api/v1/sandbox-templates` | `200 {templates: [...]}`, each `{id, build, resources: {defaults, min, max}, durability, defaultMaxRuntimeMin}` | ```json { "templates": [ { "id": "prized-base-v1", "build": "prized-base-v1-r2", "resources": { "defaults": {"vcpu": 4, "memoryMb": 8192, "diskGb": 10}, "min": {"vcpu": 1, "memoryMb": 1024, "diskGb": 10}, "max": {"vcpu": 32, "memoryMb": 131072, "diskGb": 128} }, "durability": "ephemeral", "defaultMaxRuntimeMin": 1440 } ] } ``` `id` is the name a create passes. `build` is the image a new sandbox of that name starts from today; it changes when the name is republished with a fix ([Sandboxes: Templates](https://prized.dev/docs/sandboxes#templates)), and `resources` are that build's. What each template holds is on the same page. ## Create [#create] `POST /api/v1/sandboxes` answers `201 {sandbox}` with `state: "starting"`; read it until `running`. With `?wait=running` the answer comes once it is running instead ([Waiting](#waiting)). A [large sandbox](https://prized.dev/docs/sandboxes#large-sandboxes) (more than 16 vCPU or about 58 GB of memory) may stay `starting` for a minute or two, longer than the hold: its held create answers `201` still `starting`, and you read it until `running`. ```bash curl -s -X POST "$API/sandboxes?wait=running" -H "$AUTH" -H "Content-Type: application/json" -H "Idempotency-Key: pr-1874-1" -d '{"template":"prized-agent-v1","resources":{"vcpu":2,"memoryMb":4096},"envVars":{"ANTHROPIC_API_KEY":"sk-ant-…"},"labels":{"run":"pr-1874"},"autoPauseMin":10,"autoDeleteMin":60,"maxRuntimeMin":60}' ``` The `Idempotency-Key` is yours to choose, one per sandbox: resend the create with the same key and body, and you get the first sandbox back instead of a second ([Conventions](#conventions)). | Field | Default | Rule | | --------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `template` | Required | A template id from `GET /sandbox-templates` | | `resources` | The template's defaults (capped at 2 vCPU and 4 GB on the free tier) | `{vcpu, memoryMb, diskGb}`, any of them, each within the template's `min` and `max` (and the free tier's caps); never more than 32 vCPU, 131,072 MiB, and 128 GiB | | `envVars` | None | Up to 32 names (shell identifiers of up to 64 characters), each value up to 16 KiB; set for every command, stored encrypted, never returned ([Environment variables](https://prized.dev/docs/sandboxes#environment-variables)) | | `labels` | None | Up to 32 string pairs (keys up to 64 characters, values up to 256), yours to filter on | | `autoPauseMin` | Off | Pause after this many idle minutes; `0` is off, up to 40,320 | | `autoDeleteMin` | Off | Delete after this many minutes paused; `0` is off, up to 40,320 | | `maxRuntimeMin` | The template's `defaultMaxRuntimeMin` (1,440); 60 on the free tier | Delete this many minutes after the create; up to the workspace's maximum runtime (1,440, or 60 on the free tier) | ## The sandbox object [#the-sandbox-object] ```json { "sandbox": { "id": "sbx_3f9c0a1b2d4e5f60", "state": "running", "template": "prized-agent-v1", "templateBuild": "prized-agent-v1-r2", "resources": {"vcpu": 2, "memoryMb": 4096, "diskGb": 10}, "durability": "ephemeral", "labels": {"run": "pr-1874"}, "autoPauseMin": 10, "autoDeleteMin": 60, "expiresAt": "2026-10-14T19:02:11Z", "errorReason": null, "toolboxBase": "https://…/v1/sandbox/sbx_3f9c0a1b2d4e5f60", "createdAt": "2026-10-14T17:02:11Z", "lastActiveAt": "2026-10-14T17:05:40Z" } } ``` | Field | Meaning | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `state` | `starting`, `running`, `pausing`, `paused`, `resizing`, `deleting`, `deleted`, or `error` ([States](https://prized.dev/docs/sandboxes#states)) | | `template` | The template name it was created from | | `templateBuild` | The build of that template it runs, which it keeps for life: a later republish of the name changes new sandboxes only | | `durability` | Always `ephemeral` | | `expiresAt` | When `maxRuntimeMin` deletes it | | `errorReason` | Why it is in `error`; otherwise `null` | | `autoPauseMin`, `autoDeleteMin` | `null` when off | | `toolboxBase` | The edge address for this sandbox's commands and files | | `lastActiveAt` | The last activity `autoPauseMin` counts from | ## Read [#read] | Route | Answer | | ---------------------------- | ----------------------------------------------------------------- | | `GET /api/v1/sandboxes` | `200 {sandboxes: [...]}`: every sandbox not deleted, newest first | | `GET /api/v1/sandboxes/{id}` | `200 {sandbox}`, or `404 not_found` once it is deleted | ## Lifecycle [#lifecycle] | Route | Body | Answer | | ------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | `POST /api/v1/sandboxes/{id}/pause` | | `202 {sandbox}`, `state: "pausing"`; with `?wait=paused`, `200` once it is `paused` | | `POST /api/v1/sandboxes/{id}/resume` | | `202 {sandbox}`, `state: "starting"`; with `?wait=running`, `200` once it is `running`. Checks the balance and the quota like a create | | `POST /api/v1/sandboxes/{id}/refresh` | | `204`: counts as activity for `autoPauseMin` | | `POST /api/v1/sandboxes/{id}/fork` | `{count, labels?}` | `202 {fork, sandboxes}`, below | | `POST /api/v1/sandboxes/{id}/resize` | `{resources}` | `202 {sandbox, operation}`, below | | `DELETE /api/v1/sandboxes/{id}` | | `202 {sandbox}`, `state: "deleting"`; again on a deleting sandbox is a no-op | ### Waiting [#waiting] Create and resume take `?wait=running`, and pause `?wait=paused`: the API holds the answer until the sandbox gets there, so there is nothing to poll. ```bash curl -s -X POST "$API/sandboxes/sbx_3f9c0a1b2d4e5f60/resume?wait=running" -H "$AUTH" ``` | Query | Rule | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `wait` | `running` on create and resume, `paused` on pause; anything else is `400 bad_request` with `field: "wait"` | | `waitTimeoutMs` | How long to hold, a whole number of milliseconds from `0` to `8000`; default `8000`. The answer can come up to half a second after it, while the API takes its last look at the sandbox. Anything else, or `waitTimeoutMs` without `wait`, is `400 bad_request` with `field: "waitTimeoutMs"` | * **It got there.** Resume and pause answer `200`, create `201`, with the sandbox in its new state. * **It did not get there in time.** The answer is the one without `wait` (`202`, or `201` for a create), with the sandbox as it is by then, still `starting` or `pausing`: read it until it settles, as without `wait`. A create that boots from scratch can outlast the hold. * **It failed.** The hold ends at once, with the sandbox in `error` or on its way there; the status is the one without `wait`. * **It went another way.** The hold also ends at once when the sandbox stops heading for that state: deleted (`deleting`, or gone, which answers as the request was accepted), or asked the other way by a later pause or resume (`pausing` or `starting`, or still `running` or `paused` if it had not moved yet). The status is the one without `wait`. * **A pause sent while the sandbox is still starting** holds through the boot: it shows `starting`, then `pausing`, and answers `200` once it is `paused` (or as without `wait` at the bound). * **A change that reverses one in flight** waits for the box, not the request: resume a sandbox that is still pausing and it shows `starting` until the pause has landed and it runs again; pause one still resuming and it shows `pausing` until it is paused. `200` comes only once it is really there. ### Fork [#fork] `count` is 1 to 256; `labels` replaces the parent's on every child. The answer lists the children, each `starting`; poll each until `running`. ```json { "fork": { "id": "frk_9a1c44e0b7d2f318", "parentId": "sbx_3f9c0a1b2d4e5f60", "count": 2, "status": "active", "sandboxIds": ["sbx_51d0c2aa9e8b7f64", "sbx_c8e4f1b20a3d9e75"], "createdAt": "2026-10-14T17:06:02Z" }, "sandboxes": [{"id": "sbx_51d0c2aa9e8b7f64", "state": "starting", "…": "…"}] } ``` What a child inherits, and the limits, are on [Sandboxes: Fork](https://prized.dev/docs/sandboxes#fork). ### Resize [#resize] A `paused` sandbox only. `resources` names any of `vcpu`, `memoryMb`, `diskGb`, within the template's bounds; disk never shrinks. The sandbox shows `resizing`, then `paused` again; its next resume boots fresh from the resized disk, without the paused memory. ```json { "sandbox": {"id": "sbx_3f9c0a1b2d4e5f60", "state": "resizing", "…": "…"}, "operation": { "id": "…", "status": "pending", "resources": {"vcpu": 4, "memoryMb": 8192, "diskGb": 20}, "coldRestart": true, "pausedMemoryDiscarded": true, "createdAt": "2026-10-14T17:10:00Z", "completedAt": null } } ``` Read the sandbox until it leaves `resizing`: `paused` with the new `resources` when the resize succeeded, `paused` with the old ones when it was turned down before it began, or `error` (with `errorReason`) when it failed partway, after which the sandbox cannot resume and should be deleted. To read the operation itself, send the resize with an `Idempotency-Key` and send the same request again: the replay answers `200` with the operation as it is now, its `status` moving through `pending` and `applying` to `succeeded`, `failed` (with `failureReason`), or `cancelled`. ## The edge [#the-edge] Routes below are relative to the sandbox's `toolboxBase`, with the same bearer. They run on a `running` sandbox, and wake one that is not: * **Paused or pausing:** the request resumes it, as `POST /resume` would, then waits for `running` and runs. The resume is billed and counted as activity like any other, and a refused one answers with the resume's own error (`409 insufficient_balance`, `409 paid_balance_required`, `429 sandbox_quota_exceeded`, …), with nothing run. * **Starting or resuming:** the request waits for `running`, then runs. * **The wait is up to 10 seconds,** counted from when the edge, having checked the key and looked the sandbox up, finds it not running. Requests with the same key that arrive during a wait share it, deadline included, so a later one waits only what is left of it. Past the deadline the answer is `503 sandbox_not_ready` with `Retry-After: 1`; nothing ran, so retry. * **A waiting request counts toward the sandbox's [edge limits](#edge-limits)** as a running one does: past 8 at once, the rest answer `429 rate_limited`. * **Resizing:** a request answers `409 sandbox_not_running` at once and wakes nothing. Read the sandbox until it leaves `resizing`, then retry. * **`Prized-Auto-Resume: false`** on a request turns waking off for it: a sandbox that is not `running` answers `409 sandbox_not_running`, and nothing wakes. On a `running` sandbox the header changes nothing: the request runs and counts as activity, so it still holds off [automatic pause](https://prized.dev/docs/sandboxes#automatic-pause-and-delete). To watch a sandbox without keeping it up, read `GET /api/v1/sandboxes/{id}`, which is not activity. Right after a sandbox reaches `running`, the first request can still answer `502 sandbox_unreachable` (it may have run) while the edge's tunnel comes up. So when a create or resume returns, first send a harmless command, such as `true`, until it answers `200`, as the SDK and CLI do; then send the real one. ### Run a command [#run-a-command] | Route | Body | Answer | | ----------------------- | --------------------------------- | ------------------------------------------------------- | | `POST /process/execute` | `{command, cwd?, env?, timeout?}` | `200 {stdout, stderr, exitCode, durationMs, timedOut?}` | ```bash curl -s -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"npm test","cwd":"/home/prized/work/app","env":{"CI":"1"},"timeout":600}' # {"stdout":"…","stderr":"","exitCode":0,"durationMs":8124} ``` * **How it runs.** `bash -c` as the user `prized`, in `cwd` (absolute; `/home/prized/work` by default), with the create's `envVars` and then this request's `env` on top. * **The answer is the verdict.** A command that fails still answers `200`; read `exitCode`. * **`timeout`** is in seconds, 300 by default and 3,600 at most. At the limit the command and everything it started in its process group are killed: `exitCode: -1, timedOut: true`. A process that left the group (`setsid`) runs on until it exits or the sandbox is deleted. * **Output.** Each stream keeps its first 4 MiB; the rest is dropped. For more, write to a file and download it. * **One run per request.** An exec whose answer was lost is never retried for you, since it may already have run; retry it yourself only if the command is safe to run twice. ### Files [#files] | Route | Body or query | Answer | | --------------------- | -------------------------------------- | ------------------------------------------------------------ | | `POST /files/upload` | query `path`; the raw file as the body | `200 {path, size}` | | `GET /files/download` | query `path` | The file as `application/octet-stream` with `Content-Length` | ```bash curl -s -X POST "$TOOLBOX/files/upload?path=/home/prized/work/app.py" -H "$AUTH" --data-binary @app.py curl -s "$TOOLBOX/files/download?path=/home/prized/work/out.json" -H "$AUTH" -o out.json ``` Paths are absolute and inside `/home/prized`; a path through a symlink is refused. An upload creates missing directories and replaces the file whole, never half-written. Downloads are regular files only; list or archive a directory with a command (`tar czf /home/prized/out.tgz work`) and download that. Each transfer is up to 100 MiB and 10 minutes. ### Edge limits [#edge-limits] 48 concurrent requests and 60 a second (bursts of 120) per key, and 8 concurrent and 20 a second (bursts of 40) per sandbox; past either, `429 rate_limited` with `Retry-After`. A request waiting for its sandbox to wake is one of them. The request document of an exec is at most 1 MiB. ## Errors [#errors] The control plane: | HTTP | `error` | Meaning | | ---- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `bad_request` | The body is not a JSON object, or a field has the wrong type or is out of range; for `envVars`, with `field` and a `message` stating the rules; for a `wait` or `waitTimeoutMs` the route cannot take, with `field` naming it ([Waiting](#waiting)) | | 400 | `invalid_idempotency_key` | Not 1 to 128 printable characters | | 401 | `unauthorized` | No key, or a revoked one | | 401 | `token_expired` | The key has expired, with `expiresAt` | | 403 | `key_access_denied` | The key lacks Sandboxes: Read or Write, or is limited to some boxes; `message` names what it needs | | 404 | `not_found` | No such sandbox in this workspace, a contractor's key, or a create, fork, or resize while sandboxes are switched off for this workspace (email the support address shown on https://prized.dev/docs/troubleshooting) | | 404 | `template_not_found` | `template` is missing or names no template | | 409 | `paid_balance_required` | Free-tier access is switched off for your workspace, and it has only free credit, on a create, resume, or fork: add a plan or a [top-up](https://prized.dev/docs/billing#topping-up) | | 409 | `insufficient_balance` | The balance is empty | | 409 | `workspace_held` | The workspace is on hold ([Not allowed](https://prized.dev/docs/limits#not-allowed)), on a create, resume, or fork; `message` says why. The hold paused its running sandboxes; pausing and deleting still work. Email the support address shown on https://prized.dev/docs/troubleshooting for a review | | 409 | `workspace_closing` | The workspace is being closed, on a create, resume, or fork; the owner can reopen it ([Close or erase the workspace](https://prized.dev/docs/workspaces#close-or-erase-the-workspace)) | | 409 | `template_inactive` | The template is retired; pick another | | 409 | `idempotency_conflict` | The key was used for another request | | 409 | `durability_incompatible` | `durability: "persistent"`; every sandbox is `ephemeral` | | 409 | `parent_not_running` | Fork of a sandbox that is not `running`, with its `state` | | 409 | `parent_not_forkable` | This sandbox cannot be forked. With `reason` `parent_too_big`, its machine cannot hold even one copy beside it: `field` names what does not fit (`vcpu`, `memoryMb`, or `diskGb`) and `message` says so; fork a smaller sandbox | | 409 | `fork_capacity` | The parent's machine has room for only `fits` more copies, with a `message` saying what to do: fork that many or fewer (when `fits` is above 0), try again later, or start a new sandbox and fork that, keeping the parent until it runs. The new sandbox and each copy count toward your quota: at the limit, delete sandboxes you no longer need and wait until each answers `404` (a `deleting` sandbox still counts), or top up on the free tier; never delete a parent while a copy forked from it is still `starting` | | 409 | `fork_unsupported_host` | This sandbox's machine cannot fork; create a fresh sandbox and fork that | | 409 | `resize_rejected` | With `reason`: `not_stopped` (pause it first), `resize_in_progress`, `lifecycle_in_progress`, `unchanged`, or `host_capacity` | | 422 | `unknown_field` | A field the route does not take, named in `field` | | 422 | `invalid_resources` | A shape outside the template's bounds, or over the [free tier](https://prized.dev/docs/sandboxes#free-tier)'s 2 vCPU and 4 GB (then with `"tier": "free"` and `limit`, in vCPU or MB), named in `field`; `field` `host_capacity` means that shape cannot run right now ([large sandboxes](https://prized.dev/docs/sandboxes#large-sandboxes) are not available) | | 422 | `disk_shrink_unsupported` | A resize to a smaller disk | | 422 | `invalid_max_runtime` | `maxRuntimeMin` is not a positive whole number | | 422 | `invalid_count` | A fork `count` outside 1 to `max` (256) | | 422 | `invalid_labels` | Fork `labels` that are not up to 32 string pairs | | 429 | `sandbox_quota_exceeded` | A [quota](https://prized.dev/docs/limits#sandboxes), or a [free-tier](https://prized.dev/docs/sandboxes#free-tier) limit (then with `"tier": "free"` and `limit`, in the reason's unit), would be passed, named in `reason`: `sandbox_limit`, `running_vcpu`, `running_memory`, `retained_disk`, or `max_runtime`; on a resume, `customer_disabled` means sandboxes are switched off for this workspace (email the support address shown on https://prized.dev/docs/troubleshooting). A fork adds `admitted`, how many children would have fit | | 503 | `sandbox_capacity` | No sandbox capacity is free right now. Nothing was created or billed; retry after `Retry-After` (also `retryAfterSec` in the body) | | 503 | `sandboxes_disabled`, `sandbox_create_disabled`, `sandbox_fork_disabled` | Sandboxes, or creating or forking them, are paused for maintenance; retry later | The edge: | HTTP | `error` | Meaning | | -------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `bad_json`, `missing_command`, `cwd_not_absolute`, `bad_timeout`, `bad_env` | The exec document is not the documented shape; `detail` says more where it can | | 400 | `spawn_failed` | The command could not start (a `cwd` that does not exist); `detail` has the system's words | | 400 | `missing_path`, `path_not_absolute`, `symlink_not_allowed`, `not_regular_file` | The `path` is missing, relative, through a symlink, or not a regular file | | 401 | `unauthorized`, `token_expired` | As on the control plane | | 403 | `key_access_denied` | The key lacks Sandboxes: Write | | 403 | `path_outside_root`, `permission_denied` | The path is outside `/home/prized`, or `prized` cannot touch it | | 404 | `not_found` | No such sandbox for this key, or no such file | | 409 | `sandbox_not_running` | The sandbox is not `running` and the request cannot wake it: it is resizing (retry once it leaves `resizing`), failed, or being deleted, or the request sent `Prized-Auto-Resume: false` | | 409, 429 | The resume's codes (`insufficient_balance`, `paid_balance_required`, `sandbox_quota_exceeded`, …) | The request needed to resume a paused sandbox, and the resume was refused; nothing ran | | 413 | `body_too_large`, `file_too_large` | Over 1 MiB for an exec document, or 100 MiB for a file | | 429 | `rate_limited` | Over the [edge limits](#edge-limits); `Retry-After` says when | | 502 | `sandbox_unreachable` | The sandbox did not answer. An exec may or may not have run | | 502 | `resolve_failed`, `sandbox_credentials_unavailable` | The edge could not look the sandbox up; retry | | 503 | `sandbox_not_ready` | The sandbox did not reach `running` within the edge's 10-second wait ([The edge](#the-edge)); nothing ran. Retry after `Retry-After` | | 500 | `control_plane_unavailable` | The edge could not reach the control plane to check the key | ## Not yet [#not-yet] | Not yet | Instead | | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Streaming output, or a terminal | Write output to a file and read it with a later exec or a download | | Listing, moving, or deleting files through the edge | `ls`, `mv`, and `rm` through exec | | Webhooks | Poll `GET /api/v1/sandboxes/{id}` | | An OpenAPI document, or TypeScript and Python SDKs | The routes above with `curl` or any HTTP client, or a [helper for Python or TypeScript](https://prized.dev/docs/sandboxes-python-typescript) | --- # Use Sandboxes from Python or TypeScript > A small helper for each language that creates a sandbox, runs commands, moves files, forks it, and deletes it. No SDK to install. URL: https://prized.dev/docs/sandboxes-python-typescript The [Sandbox API](https://prized.dev/docs/sandboxes-api) is plain HTTPS with a bearer key, so one short file is enough: the Python helper uses only the standard library, and the TypeScript one only `fetch`. Copy one into your project and replace the demo at its end with your own steps. From a shell, the [CLI](https://prized.dev/docs/cli#sandboxes) does the same. ## 1. Export an API key [#1-export-an-api-key] Make a key under **Dashboard → Settings → API keys** with **Full access**, or **Custom** with **Sandboxes: Write** ([Open Sandboxes](https://prized.dev/onboarding/sandboxes) also makes one). The helpers read it from `PRIZED_API_KEY`; never paste a key into code. ```bash export PRIZED_API_KEY=dcp_… ``` ## 2. Save a helper [#2-save-a-helper] **Python** ```python title="sandbox.py" import json, os, time, urllib.error, urllib.parse, urllib.request API = "https://api.prized.dev/api/v1" KEY = os.environ["PRIZED_API_KEY"] # never hardcode a key FREE = "That is a free-tier limit (https://prized.dev/docs/sandboxes#free-tier); a top-up or a plan lifts it." def call(method, url, body=None, data=None): headers = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/octet-stream"} if body is not None: data, headers["Content-Type"] = json.dumps(body).encode(), "application/json" try: with urllib.request.urlopen(urllib.request.Request(url, data, headers, method=method), timeout=3660) as r: out = r.read() return json.loads(out) if r.headers.get_content_type() == "application/json" else out except urllib.error.HTTPError as e: out = e.read() err = json.loads(out) if out.startswith(b"{") else {"error": out[:200]} hint = FREE if err.get("tier") == "free" else "" e.msg = f"{err} {hint}" # str(e) is now "HTTP Error 429: {'error': ...} That is a free-tier limit ..." raise class Sandbox: def __init__(self, id): self.id = id @classmethod def create(cls, template="prized-base-v1"): body = {"template": template, "resources": {"vcpu": 2, "memoryMb": 4096}, "maxRuntimeMin": 60} return cls(call("POST", f"{API}/sandboxes", body)["sandbox"]["id"]) def wait(self): # until running and taking commands while (sbx := call("GET", f"{API}/sandboxes/{self.id}")["sandbox"])["state"] == "starting": time.sleep(1) if sbx["state"] != "running": raise RuntimeError(f"{self.id} is {sbx['state']}: {sbx['errorReason']}") self.toolbox = sbx["toolboxBase"] self.exec("true", retry=(409, 429, 502, 503)) # a no-op, so retrying even a 502 is safe return self def exec(self, command, retry=(409, 429), **opts): # opts: cwd, env, timeout for i in range(6): try: return call("POST", f"{self.toolbox}/process/execute", {"command": command, **opts}) except urllib.error.HTTPError as e: # 409 and 429 come before the command runs if e.code not in retry or i == 5: raise time.sleep(i + 1) def write(self, path, data: bytes): call("POST", f"{self.toolbox}/files/upload?path={urllib.parse.quote(path)}", data=data) def read(self, path) -> bytes: return call("GET", f"{self.toolbox}/files/download?path={urllib.parse.quote(path)}") def fork(self, count): out = call("POST", f"{API}/sandboxes/{self.id}/fork", {"count": count}) return [Sandbox(s["id"]) for s in out["sandboxes"]] def delete(self): call("DELETE", f"{API}/sandboxes/{self.id}") sbx, kids = Sandbox.create(), [] try: print(sbx.wait().exec("python3 --version")["stdout"], end="") sbx.write("/home/prized/work/hello.txt", b"hello from Python\n") print(sbx.read("/home/prized/work/hello.txt").decode(), end="") kids = sbx.fork(2) # two live copies, hello.txt included for kid in kids: print(kid.id, kid.wait().exec("cat hello.txt")["stdout"], end="") finally: for s in [sbx, *kids]: try: s.delete() print("deleted", s.id) except Exception as e: # try the rest; maxRuntimeMin removes this one anyway print("could not delete", s.id, e) ``` **TypeScript** ```ts title="sandbox.mts" const API = "https://api.prized.dev/api/v1"; const KEY = process.env.PRIZED_API_KEY; // never hardcode a key if (!KEY) throw new Error("Set PRIZED_API_KEY first"); const FREE = "That is a free-tier limit (https://prized.dev/docs/sandboxes#free-tier); a top-up or a plan lifts it."; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); async function call(method: string, url: string, body?: unknown, data?: BodyInit): Promise { const headers: Record = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/octet-stream" }; if (body !== undefined) [data, headers["Content-Type"]] = [JSON.stringify(body), "application/json"]; const res = await fetch(url, { method, headers, body: data }); const json = res.headers.get("content-type")?.startsWith("application/json"); if (res.ok) return json ? res.json() : new Uint8Array(await res.arrayBuffer()); const err = json ? await res.json() : { error: (await res.text()).slice(0, 200) }; const hint = err.tier === "free" ? FREE : ""; throw Object.assign(new Error(`${res.status} ${JSON.stringify(err)} ${hint}`), { status: res.status }); } class Sandbox { id: string; toolbox = ""; constructor(id: string) { this.id = id; } static async create(template = "prized-base-v1") { const body = { template, resources: { vcpu: 2, memoryMb: 4096 }, maxRuntimeMin: 60 }; return new Sandbox((await call("POST", `${API}/sandboxes`, body)).sandbox.id); } async wait() { // until running and taking commands let s; while ((s = (await call("GET", `${API}/sandboxes/${this.id}`)).sandbox).state === "starting") await sleep(1000); if (s.state !== "running") throw new Error(`${this.id} is ${s.state}: ${s.errorReason}`); this.toolbox = s.toolboxBase; await this.exec("true", {}, [409, 429, 502, 503]); // a no-op, so retrying even a 502 is safe return this; } async exec(command: string, opts: { cwd?: string; env?: Record; timeout?: number } = {}, retry = [409, 429]): Promise { for (let i = 0; ; i++) { try { return await call("POST", `${this.toolbox}/process/execute`, { command, ...opts }); } catch (e: any) { // 409 and 429 come before the command runs if (!retry.includes(e.status) || i >= 5) throw e; await sleep(1000 * (i + 1)); } } } write(path: string, data: BodyInit) { return call("POST", `${this.toolbox}/files/upload?path=${encodeURIComponent(path)}`, undefined, data); } read(path: string): Promise { return call("GET", `${this.toolbox}/files/download?path=${encodeURIComponent(path)}`); } async fork(count: number): Promise { const { sandboxes } = await call("POST", `${API}/sandboxes/${this.id}/fork`, { count }); return sandboxes.map((s: any) => new Sandbox(s.id)); } delete() { return call("DELETE", `${API}/sandboxes/${this.id}`); } } const sbx = await Sandbox.create(); let kids: Sandbox[] = []; try { console.log((await (await sbx.wait()).exec("node --version")).stdout.trim()); await sbx.write("/home/prized/work/hello.txt", "hello from TypeScript\n"); console.log(new TextDecoder().decode(await sbx.read("/home/prized/work/hello.txt")).trim()); kids = await sbx.fork(2); // two live copies, hello.txt included for (const kid of kids) console.log(kid.id, (await (await kid.wait()).exec("cat hello.txt")).stdout.trim()); } finally { for (const s of [sbx, ...kids]) { try { await s.delete(); console.log("deleted", s.id); } catch (e: any) { console.log("could not delete", s.id, e.message); } // try the rest; maxRuntimeMin removes this one anyway } } ``` ## 3. Run it [#3-run-it] The demo creates a 2 vCPU, 4 GB sandbox that deletes itself after 60 minutes, runs a command, writes and reads a file, forks two copies that already have the file, and deletes all three. Python needs 3.8 or later: ```bash python3 sandbox.py ``` Node 22.18 or later runs the TypeScript file as it is; on Node 18 to 22.17, run `npx tsx sandbox.mts` instead. Older Node has no global `fetch`, so upgrade it first. ```bash node sandbox.mts ``` A run of the Python file takes about 8 seconds: ```text Python 3.12.3 hello from Python sbx_2f7f62ea3f77116f hello from Python sbx_0426aba73aa4e2ff hello from Python deleted sbx_723373199c11b589 deleted sbx_2f7f62ea3f77116f deleted sbx_0426aba73aa4e2ff ``` The TypeScript file prints the same with `v22.23.3` and `hello from TypeScript`. ## What the helper handles [#what-the-helper-handles] * **Waiting.** A create answers while the sandbox is `starting`. `wait()` reads it until `running`, keeps its `toolboxBase` (where its commands and files go), then runs `true` until the sandbox takes commands: for a moment after `running`, it can still answer `409` or `502`. * **Retries.** `exec()` retries `409 sandbox_not_running` and `429 rate_limited`, which come before a command runs, up to 5 times. It never retries `502 sandbox_unreachable`, since the command may have run; for one that is safe to run twice, pass `retry=(409, 429, 502)` in Python or `[409, 429, 502]` as the third argument in TypeScript. * **Cleanup.** `finally` deletes the sandbox and each fork even when a step or another delete fails, and `maxRuntimeMin: 60` deletes anything left behind. * **Files.** `read()` returns bytes, so archives and images arrive intact; decode text yourself, as the demo does. * **Long commands.** A command runs for up to 300 seconds unless you pass `timeout` (3,600 at most). Node's `fetch` stops waiting for an answer after 300 seconds, so in TypeScript run a job that may take that long in the background, such as `nohup ./job.sh > job.log 2>&1 &`, and read `job.log` with later commands. ## Errors [#errors] An error carries the API's status and body: the `error` code, and fields such as `reason` or `fits`. When a [free-tier](https://prized.dev/docs/sandboxes#free-tier) limit refused the request, the body has `"tier": "free"` and the helper adds a link to the limits. The ones you are most likely to see ([every code](https://prized.dev/docs/sandboxes-api#errors)): | Error | Means | Do | | ---------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | 429 `sandbox_quota_exceeded` | A quota or free-tier limit, named in `reason`. Paused sandboxes count | Delete sandboxes you are done with; on the free tier, a top-up or a plan lifts the limits | | 422 `invalid_resources` | A shape outside the template's bounds, or the free tier's 2 vCPU and 4 GB | Ask for less | | 409 `fork_capacity` | The parent's machine has room for only `fits` more copies | Fork `fits` or fewer; at `fits: 0`, fork a new sandbox instead, or wait | --- # Run Vercel AI SDK tool calls in a sandbox > Give a Vercel AI SDK agent a runCommand tool: each session gets its own sandbox, the model's commands run there through the Sandbox API, and the sandbox is deleted at the end. URL: https://prized.dev/docs/sandboxes-vercel-ai-sdk The agent below runs every command the model asks for in a sandbox, a microVM with its own kernel, instead of on the machine that runs the agent. The tool calls the [Sandbox API](https://prized.dev/docs/sandboxes-api) with `fetch`, so there is no Prized package to install. ## 1. Install the AI SDK and export your keys [#1-install-the-ai-sdk-and-export-your-keys] ```bash npm install ai zod export PRIZED_API_KEY=dcp_… export AI_GATEWAY_API_KEY=… ``` Make the Prized key under **Dashboard → Settings → API keys** with **Full access**, or **Custom** with **Sandboxes: Write**. The example uses AI SDK 7's [`ToolLoopAgent` and `tool`](https://ai-sdk.dev/docs/agents/building-agents) and Node.js 22.18 or later. Its model goes through the Vercel AI Gateway, which reads `AI_GATEWAY_API_KEY`; for another provider, install its package and change `model` ([Providers and models](https://ai-sdk.dev/docs/foundations/providers-and-models)). ## 2. Save the agent [#2-save-the-agent] ```ts title="agent.mts" import { ToolLoopAgent, tool } from "ai"; import { z } from "zod"; const API = "https://api.prized.dev/api/v1"; const headers = { Authorization: `Bearer ${process.env.PRIZED_API_KEY}`, "Content-Type": "application/json" }; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); // A path on the API, or a full URL on a sandbox's toolboxBase. The key only // goes to Prized's own hosts. Each request gives up after 3 minutes, past a // command's 120-second timeout. async function call(url: string, method = "GET", body?: object, idempotencyKey?: string): Promise { const to = new URL(url.startsWith("https://") ? url : API + url); if (to.protocol !== "https:" || !to.hostname.endsWith(".prized.dev")) throw new Error(`not a Prized address: ${to.origin}`); const res = await fetch(to, { method, body: body && JSON.stringify(body), signal: AbortSignal.timeout(180_000), headers: idempotencyKey ? { ...headers, "Idempotency-Key": idempotencyKey } : headers }); const json = res.ok ? await res.json() : await res.json().catch(() => ({})); // a cut-off answer throws if (!res.ok) throw Object.assign(new Error(`${res.status} ${JSON.stringify(json)}`), { status: res.status }); return json; } // Wait until the sandbox is running and takes commands; return its toolboxBase. async function ready(id: string): Promise { let s: any; for (let i = 0; (s = (await call(`/sandboxes/${id}`)).sandbox).state === "starting"; i++) { if (i === 120) throw new Error(`${id} is still starting after 2 minutes`); await sleep(1000); } if (s.state !== "running") throw new Error(`${id} is ${s.state}: ${s.errorReason}`); for (let i = 0; ; i++) { const res = await call(`${s.toolboxBase}/process/execute`, "POST", { command: "true" }).catch((e) => e); if (!(res instanceof Error)) return s.toolboxBase; if (i === 30 || ![409, 429, 502, 503].includes(res.status)) throw res; // those mean: not taking commands yet await sleep(1000); } } const runCommand = (toolbox: string) => tool({ description: "Run a bash command in a Linux sandbox (Ubuntu 24.04, Python 3, Node.js 22, gcc; no sudo). Files persist between calls. Send long output to a file and read its tail.", inputSchema: z.object({ command: z.string().describe("The bash command to run") }), execute: async ({ command }) => { const { exitCode, stdout, stderr, timedOut } = await call(`${toolbox}/process/execute`, "POST", { command, timeout: 120 }); return { exitCode, stdout: stdout.slice(-10_000), stderr: stderr.slice(-10_000), timedOut: !!timedOut }; }, }); const agent = (toolbox: string) => new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: "Use runCommand to run code and check your work before you answer.", tools: { runCommand: runCommand(toolbox) }, }); const task = "Count the primes below 10 million with a program, and say how long it took."; // A create whose answer is lost is sent again with the same Idempotency-Key, // which returns the sandbox it made instead of a second one. const key = crypto.randomUUID(); const create = () => call("/sandboxes", "POST", { template: "prized-base-v1", resources: { vcpu: 2, memoryMb: 4096 }, maxRuntimeMin: 60 }, key); const { sandbox } = await create().catch(create); try { const toolbox = await ready(sandbox.id); console.log((await agent(toolbox).generate({ prompt: task })).text); } finally { await call(`/sandboxes/${sandbox.id}`, "DELETE"); } ``` ## 3. Run it [#3-run-it] ```bash node agent.mts ``` The agent creates a 2 vCPU, 4 GB sandbox, writes and runs a program in it through `runCommand` as often as it needs, prints its answer, and deletes the sandbox. * **One sandbox per session.** In a chat, create the sandbox when the session starts, build the agent with its `toolbox` on every turn, and delete it when the session ends; files and installs carry over between commands. `maxRuntimeMin: 60` deletes it 60 minutes after the create, even mid-chat, so [download](https://prized.dev/docs/sandboxes-api#files) what you need before then; 60 is the [free tier](https://prized.dev/docs/sandboxes#free-tier)'s limit, and a paid workspace can set up to 1,440. * **What the model sees.** `exitCode`, `stdout` and `stderr` cut to their last 10,000 characters, and `timedOut`, true when a command passed its 120 seconds and was killed. The edge keeps the first 4 MiB of each stream, so the tool's description asks the model to send long output to a file and read its tail ([Run a command](https://prized.dev/docs/sandboxes-api#run-a-command)). A failed request reaches the model as a tool error. * **Keys.** `call` sends your Prized key only to `prized.dev` hosts. Keep keys out of commands and `envVars`: code in the sandbox, the model's included, can read them. ## Try 3 approaches at once (optional) [#try-3-approaches-at-once-optional] Do the setup the attempts share in the parent, such as a clone and an install, then [fork](https://prized.dev/docs/sandboxes#fork) it with ``await call(`/sandboxes/${sandbox.id}/fork`, "POST", { count: 3 })``: its `sandboxes` are 3 live copies, each with the parent's files and running processes. Run `agent(await ready(id))` on each copy's `id` under `Promise.allSettled`, with a different approach in each prompt, so one failed copy doesn't cut the others short, and delete the copies in a `finally`. The parent and 3 copies are 4 sandboxes of 2 vCPU and 4 GB, the whole [free tier](https://prized.dev/docs/sandboxes#free-tier), so delete any other sandbox, and let it finish deleting, before you fork. --- # Run Claude Managed Agents in Prized Sandboxes > Run each Claude Managed Agents session's tools in a Prized sandbox of its own: a self-hosted environment, a spawn script for Anthropic's worker, and the limits. URL: https://prized.dev/docs/sandboxes-claude-managed-agents [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) can run an agent's tools on machines you control, through a [self-hosted environment](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes). This guide gives each session its own sandbox: Claude runs on Anthropic's side, and the sandbox runs the agent's tool calls and holds its files. ## How it works [#how-it-works] 1. You start a session on a self-hosted environment, and Anthropic queues it. 2. `ant beta:worker poll`, on a machine you keep running, claims the session and runs `spawn.sh`. 3. `spawn.sh` creates a sandbox, installs Anthropic's `ant` CLI in it without root, and starts `ant beta:worker run`, which runs the session's tool calls. 4. A minute after the session goes idle, the worker exits, and `spawn.sh` saves the output and deletes the sandbox. 5. A message that wakes the session again starts the cycle over, in a new sandbox that gets the session's files back. Nothing connects in: the poller and the worker call out to `api.anthropic.com` over HTTPS. ## Before you start [#before-you-start] * **An agent and a self-hosted environment.** Create an agent with [Anthropic's quickstart](https://platform.claude.com/docs/en/managed-agents/quickstart). In the [Claude Console](https://platform.claude.com/workspaces/default/environments), choose **Environments → New → Self-hosted**, then **Generate environment key** on the environment's page. * **A Prized API key** with **Full access**, or **Custom** with **Sandboxes: Write** ([Sandboxes: Quickstart](https://prized.dev/docs/sandboxes#quickstart)). * **A poller machine** that stays on, Linux or macOS, with [`ant`](https://github.com/anthropics/anthropic-cli/releases), `jq`, and `prized` v0.21.0 or later ([CLI](https://prized.dev/docs/cli#sandboxes)). ## 1. Set the keys [#1-set-the-keys] On the poller machine: ```bash export ANTHROPIC_ENVIRONMENT_ID=env_… export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat01-… export PRIZED_API_KEY=dcp_… ``` The poller needs the environment key, never your Claude API key. ## 2. Save the spawn script [#2-save-the-spawn-script] Save this as `spawn.sh` in the folder you run the poller from, and run `chmod +x spawn.sh`. ```bash title="spawn.sh" #!/bin/bash # spawn.sh: `ant beta:worker poll --on-work ./spawn.sh` runs this once per work item (a session's run until it goes idle), with the session's ids and the environment key in ANTHROPIC_* variables and the work item as JSON on stdin. set -euo pipefail ANT_VERSION=1.40.0 MAX_MIN=60 # the sandbox's lifetime in minutes: up to 1440 after a top-up or a plan ANTHROPIC_WORK_SECRET=$(jq -r '.secret // empty') DIR="sessions/$ANTHROPIC_SESSION_ID" # the session's working directory as work.tgz, and a log per work item OUT="$DIR/$ANTHROPIC_WORK_ID" mkdir -p "$DIR" # Run the session in the background, so the poller can claim the next one now. ( # One run of a session at a time, so a follow-up starts from the files the run before it saved. # A run ends with its sandbox, so a lock older than that was left by a run that was killed. until mkdir "$DIR/lock" 2>/dev/null; do find "$DIR/lock" -maxdepth 0 -mmin +$((MAX_MIN + 10)) -exec rmdir {} \; 2>/dev/null || true sleep 5 done trap 'rmdir "$DIR/lock"' EXIT SECONDS=0 # from here, the sandbox's lifetime SBX=$(prized sandbox create --no-wait --template prized-base-v1 --max-runtime "$MAX_MIN" --label "anthropic-session=$ANTHROPIC_SESSION_ID") echo "sandbox: $SBX" trap 'prized sandbox rm "$SBX" || true; rmdir "$DIR/lock"' EXIT # until the session is over, a failed step deletes the sandbox # Hand the worker its keys in a file, through stdin: other users of this machine can read a command line. for v in ANTHROPIC_ENVIRONMENT_ID ANTHROPIC_ENVIRONMENT_KEY ANTHROPIC_SESSION_ID ANTHROPIC_WORK_ID ANTHROPIC_WORK_SECRET; do printf 'export %s=%q\n' "$v" "${!v}"; done | prized sandbox cp - "$SBX:~/worker.env" # A session that went idle and woke again carries on with its files. if [ -e "$DIR/work.tgz" ]; then prized sandbox cp "$DIR/work.tgz" "$SBX:~/work.tgz" prized sandbox exec "$SBX" -- 'tar xzf ~/work.tgz -C ~/work' fi # Install the ant CLI without root, and start the worker. It stops 5 minutes before the sandbox's lifetime ends, to leave time to save its output. prized sandbox exec "$SBX" -- "mkdir -p ~/.local/bin && curl -fsSL https://github.com/anthropics/anthropic-cli/releases/download/v$ANT_VERSION/ant_${ANT_VERSION}_linux_amd64.tar.gz | tar -xz -C ~/.local/bin ant" prized sandbox exec "$SBX" -- "nohup bash -c '. ~/worker.env; timeout $((MAX_MIN * 60 - 300 - SECONDS)) ~/.local/bin/ant beta:worker run --workdir /home/prized/work; touch ~/worker.done' > ~/worker.log 2>&1 &" # The worker exits a minute after the session goes idle, and leaves ~/worker.done behind. Check every 30 seconds. until prized sandbox exec "$SBX" -- 'test -e ~/worker.done' || [ "$SECONDS" -ge $((MAX_MIN * 60 - 240)) ]; do sleep 30; done # Save the working directory and the worker's log, then delete the sandbox. If saving fails, the sandbox and the session's lock stay until its lifetime ends. trap - EXIT prized sandbox exec "$SBX" -- 'tar czf ~/work.tgz -C ~/work .' prized sandbox cp "$SBX:~/work.tgz" "$DIR/work.tgz" prized sandbox cp "$SBX:~/worker.log" "$OUT.worker.log" prized sandbox rm "$SBX" || true rmdir "$DIR/lock" # after the delete, so a follow-up's create finds the sandbox's slot free ) > "$OUT.log" 2>&1 < /dev/null & ``` * **`MAX_MIN`** is each sandbox's lifetime in minutes: 60, the most the [free tier](https://prized.dev/docs/sandboxes#free-tier) allows. After a [top-up](https://prized.dev/docs/billing#topping-up) or a plan, it can be up to 1,440 (24 hours). The worker is stopped 5 minutes before the end, so its output can still be saved. * **`ANT_VERSION`** pins the `ant` release; newer ones are on [GitHub](https://github.com/anthropics/anthropic-cli/releases). * **Sessions run side by side**, a sandbox each: the script returns at once, so the poller claims the next session while this one runs. A follow-up to one session waits until its previous run has saved. * **Output** goes to `sessions//`: the working directory as `work.tgz`, replaced whole when a later run of the session saves, and for each work item the script's log as `.log` and the worker's as `.worker.log`. * **If saving fails**, for example on an archive over the 100 MB a copy takes, the sandbox is kept until its lifetime ends, and the session's next run waits until then: copy what you need with `prized sandbox cp`, then delete the sandbox and `sessions//lock`. Its id is at the top of the work item's log. ## 3. Start the poller [#3-start-the-poller] ```bash ant beta:worker poll --on-work ./spawn.sh ``` ## 4. Run a session [#4-run-a-session] Start a session on the environment and send it `Write the output of "uname -a" to hello.txt in your working directory.` While it runs, `prized sandbox ls` shows its sandbox, labelled `anthropic-session=sesn_…`. About a minute after the session goes idle, `hello.txt` is in `sessions/sesn_…/work.tgz`. ## Keys in the sandbox [#keys-in-the-sandbox] The environment key and the session's secret reach the sandbox in `~/worker.env`, which the worker loads, so the agent's tool calls can read them. The key reaches only its environment's queue: use one environment per trust boundary ([Security model](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-security)). Never pass your Claude API key or your Prized API key into a sandbox. ## Limits [#limits] | Limit | In a sandbox | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Working directory | `/home/prized/work`: tools run as the user `prized`, without root, so there is no `/workspace`. If the agent uses skills, tell it in its system prompt that they are in `/home/prized/work/skills` | | Memory stores | Not supported: they need a writable `/mnt/memory` | | Files and repositories | Anthropic does not mount them on self-hosted environments; fetch them in `spawn.sh` before the worker starts ([Stage files for a session](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-workers#stage-files-for-a-session)) | | Lifetime | 24 hours, or 60 minutes on the free tier; a session still running then loses its sandbox | | At once | 4 sandboxes of up to 2 vCPU and 4 GB on the free tier; paid quotas are on [Limits](https://prized.dev/docs/limits#sandboxes) | | Network | Outbound internet, with no allow or deny lists yet | | Region | `us-west-2` (Oregon) | Prized Sandboxes and Claude Managed Agents are both in beta. ## Troubleshooting [#troubleshooting] | Symptom | Fix | | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | The poller exits with `401 Unauthorized` and `OAuth access token is invalid` | The environment key is wrong, revoked, or for another environment than `ANTHROPIC_ENVIRONMENT_ID`: generate a new one | | The poller exits with `on-work script "./spawn.sh" is unrunnable` | Run `chmod +x spawn.sh`, and start the poller in the script's folder | | A session stays queued | Start the poller. From your own machine, with your Claude API key, `ant beta:environments:work stats --environment-id env_…` then shows `workers_polling` of 1 or more | | A follow-up waits, and its log stays empty | The session's previous run is still going, or its save failed (**If saving fails** above). A lock left by a run that was killed, for example by a reboot, clears itself a little over `MAX_MIN` + 10 minutes after that run started | | A session's log starts with a `prized:` error | Prized refused the sandbox, so that session has none; the `↳` line says what to change. `MAX_MIN` above 60 needs a top-up or a plan. An error after the `sandbox:` line while saving keeps the sandbox: see **If saving fails** above | | A session goes idle at once, and its log says `cannot create the memory store's folder` | Start the session without memory stores | --- # Sandboxes FAQ > Short answers about Sandboxes (beta): speed, forking, isolation, pricing, limits, what is not there yet, and where to get help. URL: https://prized.dev/docs/sandboxes-faq New here? [Open Sandboxes](https://prized.dev/onboarding/sandboxes) to sign up and make an API key, then run the [Quickstart](https://prized.dev/docs/sandboxes#quickstart). ## Basics [#basics] ### What is a sandbox? [#what-is-a-sandbox] Sandboxes (beta) are Linux microVMs that your code creates, runs commands in, pauses, forks, and deletes through the [API](https://prized.dev/docs/sandboxes-api) or the [CLI](https://prized.dev/docs/cli#sandboxes). Unlike a [box](https://prized.dev/docs/sandboxes#sandboxes-and-boxes), the machine you work on every day, a sandbox is for one job at a time and lives 24 hours at most. ### How fast is it? [#how-fast-is-it] A create is usually running in 4 to 5 seconds, a resume in about two, and a fork's copies within a few seconds. At busy moments a create can wait in `starting` while capacity is added, or answer `503 sandbox_capacity` with `Retry-After` and bill nothing ([Limits: Sandboxes](https://prized.dev/docs/limits#sandboxes)). ### What is inside one? [#what-is-inside-one] Ubuntu 24.04 with Python 3, Node.js 22, git, and build tools (`prized-base-v1`), or all of that plus Claude Code and Codex (`prized-agent-v1`). Each takes 1 to 32 vCPU, 1 to 128 GB of memory, and 10 to 128 GB of disk ([Templates](https://prized.dev/docs/sandboxes#templates)); more than 16 vCPU or about 58 GB of memory is a [large sandbox](https://prized.dev/docs/sandboxes#large-sandboxes). ### Where do sandboxes run? [#where-do-sandboxes-run] In `us-west-2` (Oregon). Other regions are not available yet. ## Forking [#forking] ### What does a fork copy? [#what-does-a-fork-copy] Each copy starts with the parent's memory, disk, and running processes as they were at the fork, and the parent keeps running after a brief pause, usually under a second. Copies are ordinary sandboxes, billed and counted against your quotas like any other ([Fork](https://prized.dev/docs/sandboxes#fork)). ### How many copies can one fork make? [#how-many-copies-can-one-fork-make] As many as fit on the parent's machine and in your workspace's [quotas](https://prized.dev/docs/limits#sandboxes): at most 31 copies of a 1 vCPU sandbox, 15 of a 2 vCPU one, 7 of a 4 vCPU one, and 3 of an 8 vCPU one; more memory or disk, or a busy machine, fits fewer. A fork that does not fit makes nothing and answers `409 fork_capacity` with `fits`, how many fit now: fork that many, or start a new sandbox and fork that. ### Do copies share randomness or identity? [#do-copies-share-randomness-or-identity] Each copy gets its own hostname, IP address, and `/etc/machine-id`, and a freshly reseeded kernel random number generator; its connections to other machines are closed, and loopback ones keep working. Process IDs and `boot_id` stay the parent's, and a process that was already running keeps the random state it holds in memory, so reseed or restart it after a fork before it makes keys, tokens, or IDs. ## Security and isolation [#security-and-isolation] ### How are sandboxes isolated, and what can they reach? [#how-are-sandboxes-isolated-and-what-can-they-reach] Each sandbox is its own microVM with its own kernel. It reaches the internet outbound, nothing reaches it from the internet, and sandboxes cannot reach each other ([Security and trust](https://prized.dev/docs/security#on-the-box)). ### Do I get root? [#do-i-get-root] No: commands run as the user `prized`, without sudo, so there is no `apt`. `pip` installs into a venv, and `npm install -g` needs a user prefix ([Inside a sandbox](https://prized.dev/docs/sandboxes#inside-a-sandbox)). ### What can Prized see? [#what-can-prized-see] Commands and files pass through Prized's edge, which checks your API key on every request; the key never enters the sandbox, and the [`envVars`](https://prized.dev/docs/sandboxes#environment-variables) you set are stored encrypted and never returned by any route. A running sandbox reports resource vitals, listening ports, and its top processes, never file names, file contents, or terminal output ([Security and trust](https://prized.dev/docs/security#data-at-rest-and-in-flight)). ### What happens to my data when a sandbox is deleted? [#what-happens-to-my-data-when-a-sandbox-is-deleted] Deleting a sandbox, by hand or at the end of its lifetime, erases its disk, memory, and environment variables with no undo. What the workspace keeps after a delete, and what erasing the workspace removes, is on [Data retention](https://prized.dev/docs/data-retention#records-retained-after-deletion). ## Pricing and limits [#pricing-and-limits] ### How much does it cost? [#how-much-does-it-cost] Per second, with no minimum: running, by vCPU, memory, and disk; paused, by disk and saved memory alone ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)). vCPU: $0.04 per vCPU-hour running; paused, not charged. Memory: $0.0125 per GB-hour running; paused, $0.0001 per gb-hour, for the saved memory. Disk: $0.0001 per GB-hour running; paused, $0.0001 per gb-hour. 1 vCPU, 2 GB, 10 GB disk: $0.066 an hour running, $0.0012 an hour paused. 2 vCPU, 4 GB, 10 GB disk: $0.131 an hour running, $0.0014 an hour paused. 4 vCPU, 8 GB, 10 GB disk (the default): $0.261 an hour running, $0.0018 an hour paused. 8 vCPU, 16 GB, 10 GB disk: $0.521 an hour running, $0.0026 an hour paused. 32 vCPU, 128 GB, 10 GB disk (the largest): $2.881 an hour running, $0.0138 an hour paused. ### What do I get for free? [#what-do-i-get-for-free] New workspaces get $30 of free credit with no card. On the free credit alone you can run 4 sandboxes at once, each up to 2 vCPU, 4 GB, and 60 minutes, so a free fork is a parent plus 3 copies; a [top-up](https://prized.dev/docs/billing#topping-up) or a plan lifts the workspace to the full quotas ([Free tier](https://prized.dev/docs/sandboxes#free-tier)). ### How long can a sandbox live, and what does pausing do? [#how-long-can-a-sandbox-live-and-what-does-pausing-do] Up to 24 hours from the create, paused time included (60 minutes on the free tier), then it is deleted. A paused sandbox keeps its memory and disk and bills for storage alone, and on resume every process carries on where it stopped ([Pause and resume](https://prized.dev/docs/sandboxes#pause-and-resume)). ### What happens at a limit, or when my credit runs out? [#what-happens-at-a-limit-or-when-my-credit-runs-out] A request past a quota makes nothing and answers `429 sandbox_quota_exceeded`, naming the limit in `reason` ([Limits: Sandboxes](https://prized.dev/docs/limits#sandboxes)). At a zero balance, with overage off, running sandboxes pause until you add credit and resume them, and are still deleted when their lifetime ends ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)). ## What's not there yet [#whats-not-there-yet] ### Can I bring my own image? [#can-i-bring-my-own-image] Not yet: no templates from your own Dockerfile or image, and no saving a sandbox as a template. Install what you need once in a sandbox, then [fork](https://prized.dev/docs/sandboxes#fork) it for each job. ### Is there an SDK? [#is-there-an-sdk] No SDK packages are published yet. Use the [Sandbox API](https://prized.dev/docs/sandboxes-api) from any HTTP client, a [helper for Python or TypeScript](https://prized.dev/docs/sandboxes-python-typescript), or `prized sandbox` from the [CLI](https://prized.dev/docs/cli#sandboxes). ### Can I stream a command's output? [#can-i-stream-a-commands-output] Not yet: an exec answers when the command ends, with up to 4 MiB of each stream. For a long job, write its output to a file and read it with a later exec or a download ([Run a command](https://prized.dev/docs/sandboxes-api#run-a-command)). ### What else is missing? [#what-else-is-missing] Other regions, GPUs, egress allow and deny lists, SSH or a terminal into a sandbox, preview links to its ports, more than 32 vCPU or 128 GB, and lifetimes past 24 hours. [Not yet](https://prized.dev/docs/sandboxes#not-yet) lists each with what to use instead. ## Getting help [#getting-help] ### Where do I get help? [#where-do-i-get-help] Email the support address shown on https://prized.dev/docs/troubleshooting with the sandbox id and the error from the response; common errors and their fixes are on [Troubleshooting: Sandboxes](https://prized.dev/docs/troubleshooting#sandboxes). Report a vulnerability to the same address, before publishing it if you can ([Security and trust](https://prized.dev/docs/security#certifications-and-disclosure)). --- # Dashboard > A map of the signed-in app at prized.dev/dashboard: every screen, its controls, and where each setting lives. URL: https://prized.dev/docs/dashboard The same fleet view, with live charts and processes, runs in your terminal too: `prized dashboard` ([Terminal dashboard](https://prized.dev/docs/cli#terminal-dashboard)). ## Layout [#layout] | Where | What | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Sidebar | Boxes, Secrets, Connectors, Activity, **Settings**, and **Help** at the foot, with what is left of the [free credit](https://prized.dev/docs/billing#free-credit) while you are on it | | Help, foot of the sidebar | **Restart onboarding** walks you through the tour again (your workspace stays as it is, and teammates are not sent into it), then the Docs, Quickstart, CLI reference and Troubleshooting, each in a new tab | | Workspace name, top of the sidebar | A menu: your email, **Settings**, **Switch workspace**, **Sign out**. The **+** beside it creates a box | | Settings | **Account** (Profile, Security), then **Workspace** (General, Members, Policies, Billing, Audit log, Data and privacy) | | Top banner | Money needs attention ([When we tell you](https://prized.dev/docs/billing#when-we-tell-you)), the workspace is closing (with **Reopen**), or a signed-in machine's CLI is too old to [update itself](https://prized.dev/docs/cli#updates) | | Bottom-right card | Until a machine signs in with `prized login`: the CLI [install command](https://prized.dev/docs/quickstart#2-install-the-cli) with your sign-in link, for macOS or Linux, or Windows; **x** hides it for two weeks | ## Screens [#screens] | Screen | Route | What you can do | Details | | -------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Boxes | `/dashboard` | See your boxes, then your team's, with their state and vitals; copy a `prized ssh` command | [Boxes](https://prized.dev/docs/boxes) | | Boxes: Deleted | `/dashboard/deleted` | Undo a box delete; snapshots whose box is gone | [Deleted](#deleted) below | | New box | `/dashboard/boxes/new` | Pick a size, name, region, storage and per-box vars; create | [Create](https://prized.dev/docs/boxes#create) | | Box: Overview | `/dashboard/boxes/` | State, `prized ssh` command, **Open terminal**, machine facts, live numbers, ports and their links, activity; pause or resume, resize, move region, auto-pause, share a port, snapshot, delete | [Boxes](https://prized.dev/docs/boxes) | | Box: Snapshots | `/dashboard/boxes//snapshots` | Take, restore, delete and name snapshots; the automatic schedule | [Snapshots](https://prized.dev/docs/snapshots) | | Box: Settings | `/dashboard/boxes//settings` | Pause deadline, quiet hours, phone access, per-box vars, delete | [Boxes](https://prized.dev/docs/boxes) | | Box: Terminal | `/dashboard/boxes//terminal` | A shell in the box's tmux session, or herdr's once the box has it | [Sessions](https://prized.dev/docs/sessions), [herdr](https://prized.dev/docs/herdr) | | Box: Desktop | `/dashboard/boxes//desktop` | A Linux desktop or one Chrome window, streamed | [Desktop](https://prized.dev/docs/desktop) | | Box: Metrics | `/dashboard/boxes//metrics` | Live and historical cpu, memory, network, disk, ports and processes | [Telemetry retention](https://prized.dev/docs/data-retention#telemetry-retention-windows) | | Box: Files | `/dashboard/boxes//files` | Browse, edit, upload, download, rename and delete files | [Files](#files) below | | Connectors | `/dashboard/connectors` | Mount a provider credential into a box | [Connectors](https://prized.dev/docs/connectors) | | Secrets | `/dashboard/secrets` | Create, rotate, grant and delete brokered secrets; see what each was spent on | [Secrets for agents](https://prized.dev/docs/credentials#secrets-for-agents) | | Activity | `/dashboard/workspace/activity` | What the team is doing now | [Teams: Team activity](https://prized.dev/docs/teams#team-activity) | | Settings: Profile | `/dashboard/settings` | Your name and email | [Account](#account) below | | Settings: Security | `/dashboard/settings/security` | Your sign-in providers and browser sessions | [Account](#account) below | | Settings: General | `/dashboard/workspace` | Workspace name, icon, domain, cloud federation | [Workspace settings](#workspace-settings) below | | Settings: Members | `/dashboard/workspace/members` | Invite link, **Add member**, members and roles | [Workspaces](https://prized.dev/docs/workspaces) | | Settings: API keys | `/dashboard/workspace/api-keys` | Keys for machines and scripts: make, list, revoke | [Workspaces: API keys](https://prized.dev/docs/workspaces#api-keys) | | Settings: Policies | `/dashboard/workspace/policies` | Spend and box caps, largest size, auto-pause floor, quiet hours, audit retention | [Teams: Policies](https://prized.dev/docs/teams#policies) | | Settings: Billing | `/dashboard/billing` | Plan, balance, top-ups, overage, what each box burns, the Stripe portal | [Billing](https://prized.dev/docs/billing) | | Settings: Audit log | `/dashboard/workspace/audit` | Who did what; filter, download a CSV, set up the scheduled export | [Teams: Audit log](https://prized.dev/docs/teams#audit-log) | | Settings: Data and privacy | `/dashboard/workspace/data` | Zero data retention; close or erase the workspace | [Data retention](https://prized.dev/docs/data-retention) | Owners and members also get **Sandboxes** in the sidebar, under Boxes: | Screen | Route | What you can do | Details | | -------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | | Sandboxes | `/dashboard/sandboxes` | Every sandbox with its template, shape, state and hourly cost, against the workspace's quotas; create, pause, resume, delete. Before the first: a key for sandboxes in one click and the first request | [Sandboxes: Quickstart](https://prized.dev/docs/sandboxes#quickstart) | | Sandbox | `/dashboard/sandboxes/` | Countdowns to auto-pause and deletion, live CPU, memory and processes, events | [Sandboxes: States](https://prized.dev/docs/sandboxes#states) | | Sandboxes: Templates | `/dashboard/sandboxes/templates` | The templates, their shapes and what each costs; start a sandbox from one | [Sandboxes: Templates](https://prized.dev/docs/sandboxes#templates) | **API docs**, after the tabs on these screens, opens the [Sandbox API](https://prized.dev/docs/sandboxes-api) reference in a new tab. The bottom-right install card stays off these screens, which carry their own install step. ## Boxes [#boxes] One card per box, grouped **Needs attention**, **In progress**, **Running** and **Paused**, with **All**, **Running** and **Paused** filters and **New box** in the header. A card shows the box's state, size, region, cpu, memory and disk, and whether it is in use, idle, or due to pause; hover to copy `prized ssh `, click to open the box. The cards are the boxes you own. In a [shared workspace](https://prized.dev/docs/workspaces), everyone else's sit under them in a folded **Team** section, each card naming its owner; it starts open when you own no box yet, and the state filters apply to it too. An owner's own cards also include any box no current member owns, such as a removed member's. Owners and members still open any box, from the Team section, by its link, on [Team activity](https://prized.dev/docs/teams#team-activity), or from the CLI. A [contractor](https://prized.dev/docs/teams#roles) sees only their own, with no Team section. An owner who is the workspace's only member and has a box also sees **Invite your team** under the cards: **Copy invite link** copies the [invite link](https://prized.dev/docs/teams#add-your-team), and **x** hides it for 90 days. It goes once someone joins. ## New box [#new-box] **New box** on Boxes, or the **+** beside the workspace name; the summary card at the right holds **Create box**. | Control | What it does | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name | A fruit name is suggested; the dice picks another. No rename later. | | Region | Where the box runs ([Regions](https://prized.dev/docs/boxes#regions)), the region nearest you unless you pick another; the menu lists every region fastest first, by the round trip timed from your browser. | | Size | Seven sizes, Nano to Extra Large, with prices. One your balance cannot keep running is refused, with the reason, unless the plan offer is checked ([Starting and resizing boxes](https://prized.dev/docs/billing#starting-and-resizing-boxes)). | | Storage | **Disk** or **Infinite storage**, shown where your workspace can have [infinite storage](https://prized.dev/docs/boxes#infinite-storage); it starts on what a create gets without asking. **Infinite storage** is greyed out, with the reason, on a size or region that cannot have it. | | More options › Env vars | `NAME=value`, one per line ([Per-box vars](https://prized.dev/docs/environments#per-box-vars)). No default [environment](https://prized.dev/docs/environments) applies here; pass one from the CLI or API. | | More options › Restricted | Closes every credential channel to the box, permanently ([Safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties)); locked on for a [contractor](https://prized.dev/docs/teams#roles). | | Plan offer | Owners see a checked **Switch to the $X/mo plan** when the box would outgrow the plan (uncheck to stay); with no plan and an empty balance, the button reads **Continue to checkout**. | | Create box | A refusal (a taken name, the box limit, too little balance, a [workspace policy](https://prized.dev/docs/teams#policies)) shows its reason under the button; a closing workspace shows no form. | ## The box page [#the-box-page] Tabs: **Overview**, **Terminal**, **Desktop**, **Metrics**, **Files**, **Snapshots**, **Settings**. On every tab the header carries **Pause** (or **Resume**) and a **…** menu with the other actions. On an [infinite storage](https://prized.dev/docs/boxes#infinite-storage) box, **Pause** asks first, since the box stops: running processes end and it boots fresh on resume, with your files kept; the auto-pause settings say the same. ### Overview [#overview] The top line shows the state and since when (**Running for 2d 4h**, **Paused 3d ago**), and says so when a running box's report is more than two minutes late. Beside it: the `prized ssh` command (click to copy) and **Open terminal**, which reopens the way in you used last; its chevron lists them all: * **Terminal:** the browser terminal. * **Cursor**, **Terminal app:** that app on this computer, on the box, through the SSH stanza `prized setup` wrote ([Sessions](https://prized.dev/docs/sessions)). * **Claude Code**, **Codex:** the browser terminal in the agent's session (`claude` or `codex`), attached if `prized agents run` started it, an empty shell otherwise ([Agents](https://prized.dev/docs/agents)). Below it, two tiles and then **Activity**, the box's history: | Tile | Shows | Click | | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Machine | **Size**, **Disk** (**Storage** on an infinite-storage box), **Region**, the **Auto-pause** menu, **Pauses** (a set deadline), **Snapshots** (the last one, the schedule), **Rate**, **Created**, **Ports** | **Size** opens Resize (the box restarts on the new size, [Resizing](https://prized.dev/docs/boxes#resize)); **Region** opens Move, with more than one region ([Regions](https://prized.dev/docs/boxes#regions)); **Snapshots** opens its tab; a port opens its card | | Now | CPU, memory and disk as last reported, and **Up next**: an idle pause, a deadline, or a due snapshot | Opens Metrics | **Ports**, the Machine tile's last row, has a chip for every port the box is listening on and every port shared from it. A shared port's chip is blue with ↗ (outlined while nothing listens on it); shared ports come first, and past two rows the rest fold behind **N more**. Listening ports come from the box's own report, so a new server shows up within about a minute; a [zero data retention](https://prized.dev/docs/data-retention#zero-data-retention) workspace keeps no such report, so only its links show. Click a chip for its card: | Control | What it does | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Workspace**, **Link**, **Public** | Who can open the port: members of your workspace after signing in, anyone you send the link to, or anyone at all. An option you cannot pick is greyed out with the reason ([Share a port](https://prized.dev/docs/ports#share-a-port)). | | 1 day, 7 days, 30 days | How long a **Link** or **Public** share lasts. | | The button | Shares the port as picked: **Share with workspace**, **Create link for 7 days**, **Make public for 7 days**. On a shared port, picking someone else makes a new link and the old one stops working. | | Copy | Copies the link. A **Link** share's address is shown only right after it is made; afterwards **New link** replaces it with a fresh one. | | **Stop sharing** | Ends the link for everyone on it. | **+** at the end of the row shares a port nothing listens on yet. The **…** menu: **Open terminal**, **Snapshot now** ([Snapshots](https://prized.dev/docs/snapshots)), **Copy box id**, **Settings**, and **Delete box…** (type `delete `; the undo is **Recently deleted**, [Deleting a box](https://prized.dev/docs/data-retention#deleting-a-box)). Forking is `prized fork` or the API ([Fork a box](https://prized.dev/docs/snapshots#fork-a-box)). A refused action shows its reason under the title; what each action does is on [Boxes](https://prized.dev/docs/boxes) and [Snapshots](https://prized.dev/docs/snapshots). ### Settings [#settings] | Section | Row | Control | | --------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sleep | Auto-pause | The same menu as on the Overview. | | Sleep | Pause at | No deadline, 1, 4, 12 or 24 hours, or a custom window (`90m`, `36h`, `3d`), then **Set**; hidden while paused. | | Sleep | Quiet hours | **Exempt** (owner only), while the workspace has [quiet hours](https://prized.dev/docs/teams#quiet-hours). | | Access | Phone access | **Set up** shows host, port and username; **Generate password** shows it once, **Regenerate password** replaces it, **Revoke** removes it ([From your phone](https://prized.dev/docs/codex#from-your-phone)). | | Variables | Variables | **Edit** the per-box `NAME=value` list ([Per-box vars](https://prized.dev/docs/environments#per-box-vars)). | | Secrets | Mounts | **Mount** a secret [available to this box](https://prized.dev/docs/teams#scoped-secrets) under an env name, or **Unmount** one. Needs a running box; a restricted box refuses mounts. | | Danger | Delete box | The same typed confirmation as the menu. | ### Terminal [#terminal] The box's tmux session `main`, the one `prized ssh` opens: opening it wakes a paused box, and closing the tab loses nothing ([Sessions](https://prized.dev/docs/sessions)). * **Other sessions:** `?session=NAME` opens another session, created if missing. * **herdr:** on a box with [herdr](https://prized.dev/docs/herdr#from-the-dashboard-and-your-phone), **tmux** and **herdr** at the top right switch between the two; the browser remembers the pick per box, and `?mux=herdr` opens herdr directly. In herdr, an agent that finishes or needs you shows as a toast over the terminal. Very large notification messages are ignored to keep the terminal responsive. * **Copy:** select to copy (Cmd+C on a Mac, Ctrl+Insert or the right-click menu elsewhere); Ctrl+C goes to the shell. On a phone: * **Taps and drags:** a tap is a click (herdr's buttons, tmux's panes and status line, Claude Code's own) and a drag scrolls. * **The keyboard:** a tap on the line where the cursor is, such as Claude Code's prompt or a shell's, also brings the keyboard up; elsewhere the keyboard stays as it is. * **The key row:** the row under the terminal starts with the keyboard key, which brings the keyboard up and puts it away, then Esc, Tab, Ctrl, ^C and the arrows. Ctrl applies to the next key you type and brings the keyboard up for it: Ctrl then c is Ctrl+C, and Ctrl, b, then c opens a new tmux window (a new tab in herdr). ### Desktop [#desktop] Pick **Desktop** or **Browser**, then **Open desktop** or **Open browser** ([Desktop](https://prized.dev/docs/desktop)). ### Metrics [#metrics] Pick a range at the top right: **Last hour** is live; **Last 6 hours**, **Last 24 hours**, **Last 7 days** and **Last 30 days** are stored, drawn as an average with a max band, paused spans shaded and lifecycle events marked. Click a chart to pin a moment: the processes and listening ports below switch to the nearest stored snapshot (**Unpin** or Esc releases it). Live, the page also shows per-core load, interfaces, mounts and tmux sessions. The page never wakes a paused box; retention is on [Data retention](https://prized.dev/docs/data-retention#telemetry-retention-windows). ### Files [#files] A file browser as the box's login user, opening in your home directory (`~`); it needs a running box and never wakes one. * **Find a folder:** type a path in the filter (`~/projects` or `/var/log`, then Enter) or click the breadcrumb; **Display** holds the sort and **Show hidden files** (⌘⇧.). * **Change files:** a row's **…** menu has Open, Rename, Download and Delete…; **New folder** and **Upload** (or a drop on the listing) add to the folder. * **Uploads:** keep the local file unchanged until it finishes. A detected change cancels the upload. One that fails leaves any existing file of that name as it was; one that does not stop promptly ends the file-browser connection (**Reconnect** tries again). If the connection drops near the end of an upload, check the file before uploading it again. While a transfer runs, closing or reloading the tab asks first. Keys: ↑ ↓ move, Enter opens, Backspace goes up, `/` focuses the filter, Esc closes the file or clears the filter. A text file within the edit cap opens in an editor with **Save**; a bigger one shows its first megabyte read-only, and a binary file offers **Download**. Caps are on [Limits](https://prized.dev/docs/limits#commands-and-files). ## Snapshots [#snapshots] The box page's **Snapshots** tab lists its snapshots, newest first. Beside the heading: the [automatic schedule](https://prized.dev/docs/snapshots#automatic-snapshots) (every hour to every week, and how many to keep) and **New snapshot**. A row's **…** menu: **Restore into a new box** and **Delete** (**Cancel snapshot** while it is being taken) ([Snapshots](https://prized.dev/docs/snapshots)). ### Deleted [#deleted] The **Deleted** tab of Boxes: **Recently deleted** holds the undo copies of deleted boxes, a disk or an infinite storage box's files (**Restore the box**, **Delete now**); **Snapshots** lists manual snapshots whose box is gone, with the same menu as above. A box restored from infinite storage shows **Restoring files** and how many have been copied until it starts. ## Connectors [#connectors] One row per provider; **Connect** asks for a running box, the credential, the env name and, for ClickHouse, the HTTPS endpoint ([Connectors](https://prized.dev/docs/connectors)). ## Secrets [#secrets] **New secret** takes a name, a value, the allowed hosts (a name such as `OPENAI_API_KEY` fills in its provider's host), and **Available to**: **All boxes**, **Some boxes**, or **Members**. A row's **…** menu: **Details** (grants with **Revoke**, **grant a box**, **grant a member**, usage), **Rotate** (the placeholder stays, so mounted boxes keep working), **Delete**. Values are write-only once saved ([Secrets for agents](https://prized.dev/docs/credentials#secrets-for-agents), [Teams: Scoped secrets](https://prized.dev/docs/teams#scoped-secrets)). ## Workspace settings [#workspace-settings] Owner-only controls say so to members; while the workspace is closing, every control is read-only. | Page | Section | Rows | | ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | General | General | **Icon** (your domain's favicon until you **Upload** one; **Remove** clears it), **Name**, **Domain** (**Claimed**, then **Auto-join** and **Restrict membership**) ([Create one](https://prized.dev/docs/workspaces#create-one)) | | General | Cloud federation | **Connect** Amazon Web Services: **Open AWS** opens the console on a pre-filled stack; each connected role then shows **Connected**, **Check failed** (with the reason) or **Not verified**, with **Verify** and **Remove** ([AWS access](https://prized.dev/docs/aws)). Owner only; members read; not shown to contractors | | Members | Invite | **Invite link** (**Copy**, **New link**) ([Invite people](https://prized.dev/docs/workspaces#invite-people)) | | Members | Members | For the owner, **Add member** (emails the invite link to an address) ([Invite people](https://prized.dev/docs/workspaces#invite-people)), and on each row a role select (**member** or **contractor**) and **Remove**; **Leave** on your own row ([Remove someone](https://prized.dev/docs/workspaces#remove-someone)) | | API keys | API keys | **New key** (named, shown once with a copy button); every key, one per signed-in machine plus any made for a script (a contractor's own), with **Revoke** and **Revoke all** ([API keys](https://prized.dev/docs/workspaces#api-keys), [Credentials Prized issues](https://prized.dev/docs/security#credentials-prized-issues)) | | Data and privacy | Data and privacy | **Zero data retention**: **Turn on** (typed confirmation) or **Turn off** on a [custom plan](https://prized.dev/docs/billing#custom-plans), **Talk to us** otherwise ([Zero data retention](https://prized.dev/docs/data-retention#zero-data-retention)) | | Data and privacy | Danger zone | **Close workspace** and **Erase now**, each confirmed by typing the workspace's name ([Closing a workspace](https://prized.dev/docs/data-retention#closing-a-workspace)) | ## Activity [#activity] Totals (boxes running, sessions attached, spend today and this cycle), then **Boxes** (owner, state, idle time, sessions, last prompt and exec, spend), **Members** (boxes, sessions, last active, spend; not shown to contractors), and **Recent** (the newest 50 audit rows, 15 a page). The terms are on [Teams: Team activity](https://prized.dev/docs/teams#team-activity). ## Policies [#policies] **Settings › Policies**. Each rule saves on its own; clear its field (or pick **Any size**) and save to turn it off. What each rule does is on [Teams: Policies](https://prized.dev/docs/teams#policies). | Section | Rows | | ------------- | --------------------------------------------------------------------------------------------------------------------- | | Member limits | **Spend per member**, **Boxes per member**, **Largest size per member**, **Auto-pause floor** | | Quiet hours | **Pause the workspace nightly**, then **Nightly from** and to, the days **On** and **All day on**, and a **Timezone** | | Audit log | **Retention**, in days | ## Audit log [#audit-log] **Settings › Audit log**. **SSH certificate issued** records a credential being issued, not a successful connection. Kinds, origins and the export contract are on [Teams: Audit log](https://prized.dev/docs/teams#audit-log). | Control | What it does | | ------------ | -------------------------------------------------------------------------------------------------------------- | | Filters | Member, box, kind, and range: 24 hours, 7 days (the default), 30 days, 90 days, or everything kept | | The table | When, member and credential kind, action, box, detail; client-reported rows say **reported**; 25 rows a page | | Download CSV | Every row of the current filters and range, up to 50,000 | | Export | Owner only: the [scheduled export](https://prized.dev/docs/teams#scheduled-export) destination, with its last delivery and error | ## Billing [#billing] **Settings › Billing**: everyone reads it; only the owner sees the controls. | Section | What it holds | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Top | The balance and how long it lasts, the plan and its renewal date, what your boxes cost around the clock | | Plan | A line when your boxes fit a smaller plan or outgrow this one; the ladder (**Choose**, **Upgrade now** or **Switch next cycle**, where a second click confirms and names what bills today); **Resume** while a cancel is scheduled; **Cancel plan** | | Credit | **Add credit** for a [top-up](https://prized.dev/docs/billing#topping-up), the [Overage](https://prized.dev/docs/billing#overage) switch, the rollover rule | | Boxes | Every live box with its size, state and hourly rate (a contractor's own) | | Recent activity | The last 50 ledger lines | | Payment and invoices | The Stripe portal: card, invoices, receipts ([Billing](https://prized.dev/docs/billing#choosing-and-changing-the-plan)) | ## Account [#account] Under **Settings**, **Account** is you, not the workspace. * **Profile:** your name, your email from your sign-in provider, and **Theme**: **System** (the default, following your OS), **Light** or **Dark**. * **Security:** **Sign-in methods** (Google and GitHub; **Connect** links another provider by its verified email, **Disconnect** unlinks any but the last) and **Sessions** (each browser and the network address it signed in from; **Sign out** one, or **Sign out other sessions**). API keys have their own page under [workspace settings](#workspace-settings); the sign-in model is on [Identity and access](https://prized.dev/docs/security#identity-and-access). ## Where a setting lives [#where-a-setting-lives] | Setting | Where | | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | Box name | New box, at create; no rename later | | Auto-pause | Box page, Overview or Settings, **Sleep** | | Pause deadline; a box's quiet hours exemption (owner) | Box page, Settings, **Sleep** (**Pause at**, **Quiet hours**) | | Size, region | Box page, Overview, **Size** and **Region** | | Per-box env vars | Box page, Settings, **Variables** | | Snapshot schedule | Box page, **Snapshots** tab | | Phone SSH password | Box page, Settings, **Access** | | Shared ports | Box page, Overview, the Machine tile's **Ports** row | | Secrets mounted on a box | Box page, Settings, **Secrets** | | Who a secret is available to, what it was spent on | Secrets, **Details** | | Environments (what new boxes inherit, the default, safe for third parties) | CLI `prized environments` or the API; not in the dashboard | | Workspace name, icon, domain | Settings, **General** | | AWS roles the boxes assume | Settings, **General**, **Cloud federation** (owner) | | Invite link, members, member roles (owner) | Settings, **Members** | | API keys and device SSH keys | Settings, **API keys** | | Spend and box caps, largest size, auto-pause floor, quiet hours, audit retention | Settings, **Policies** (owner) | | Audit export | Settings, **Audit log**, **Export** (owner) | | Plan, top-ups, overage, card, invoices | Settings, **Billing** (owner) | | Zero data retention, close, erase | Settings, **Data and privacy** | | Your name, light or dark theme | Settings, **Profile** | | Sign-in providers, browser sessions | Settings, **Security** | | CLI usage telemetry | Not in the dashboard: [CLI: usage telemetry](https://prized.dev/docs/cli#usage-telemetry) | | Dashboard analytics | Your browser's Do Not Track setting: [Privacy](https://prized.dev/docs/privacy#turning-it-off) | --- # Workspaces > One account for the whole team; boxes, balance, and secrets shared by everyone in it. URL: https://prized.dev/docs/workspaces ## What a workspace is [#what-a-workspace-is] Every sign-in belongs to at least one workspace; a solo account is a workspace of one. Owners and members see the same boxes and spend the same balance (the dashboard's Boxes page lists your own first, with the rest folded under **Team**: [Dashboard: Boxes](https://prized.dev/docs/dashboard#boxes)); a [contractor](https://prized.dev/docs/teams#roles) sees only the boxes they own. Roles, policies, and the rest of what a team gets are on [Teams](https://prized.dev/docs/teams). ## Create one [#create-one] Your first sign-in creates one, named after your company domain or after you. Rename it under **Dashboard → Settings → General**, where the owner can also claim your email domain, with two switches: * **Auto-join:** anyone with an @yourdomain address joins without approval. New sign-ins with a provider-verified address on the domain land in your workspace instead of an empty one. A personal mail domain (Gmail, Outlook, and the like) can never be claimed. * **Restrict membership:** only @yourdomain addresses may be members. The invite link admits only addresses on the domain. A domain belongs to one workspace: if a colleague claimed it first with joining open, your first sign-in lands you in theirs, and `prized login` prints `✓ signed in as you@yourdomain.com · Acme (shared)`. ## Invite people [#invite-people] **Dashboard → Settings → Members** shows the invite link: anyone who opens it and signs in becomes a member (the owner can then make them a [contractor](https://prized.dev/docs/teams#roles)). **Add member** emails that link to an address you type, from Prized with replies going to you; up to 20 invites an hour per workspace. **New link** replaces it, and the old one stops working at once, including in invites already emailed. Teammates on a claimed domain with joining open need no link. What your teammate sees, step by step: [Add your team](https://prized.dev/docs/teams#add-your-team). Joining keeps the workspace a teammate's own sign-up made. While theirs stays unused (no box or sandbox ever, no payment, and no plan, secret, invite link, claimed domain or other member now), the team is their default; they can switch back to it, or [erase it](#close-or-erase-the-workspace). ## Switch between workspaces [#switch-between-workspaces] Click the workspace name at the top of the sidebar, then **Switch workspace**. Everything there, API keys included, is scoped to the current workspace. Each browser remembers your pick, and `prized login` makes its key for the workspace that browser has open. A browser where you never switched opens the workspace you own, or, if you joined a team and yours is unused, the team. ## Remove someone [#remove-someone] **Dashboard → Settings → Members → Remove** (owner only). Their dashboard access ends at once, and every credential they created for this workspace is revoked: API keys (`prized` on their machine and their scripts stop working), unused install links, and any [phone access password](https://prized.dev/docs/codex#from-your-phone). The invite link is replaced, so their copy cannot let them back in; boxes, secrets, and the balance stay. Their boxes, like a leaving member's, stop receiving new [AWS identity tokens](https://prized.dev/docs/aws#on-the-box). The domain never readmits a removed member, even with joining open; only a link shared after the removal does. A member can leave from their own row (the link is not replaced); the owner cannot leave. ## API keys [#api-keys] **Dashboard → Settings → API keys** lists every key in this workspace. An API key (`dcp_…`) acts as the member who made it, with their role, from the CLI or any script ([API](https://prized.dev/docs/api)). A key is shown once; only its hash is stored. Each row shows what the key can do, when it expires, its last use, and its requests over the last 30 days. * **One per machine.** `prized login` makes a key named after the machine; its row shows the CLI version it last reported, and whether it registered an SSH key for [Claude Desktop remote](https://prized.dev/docs/claude-desktop). A machine's key never expires while you use it; after 180 days unused it expires, and `prized` asks you to sign in again. * **One per script or agent.** **New key** asks for a name, what the key may do, when it expires and which boxes it reaches, then shows it once, with a copy button; make one for each script so you can revoke it alone. * **One per agent sign-in.** The key a coding agent got when it made your account with an emailed code ([Agent sign-in](https://prized.dev/docs/agent-sign-in)) is listed by the agent's name and marked as an agent's key: boxes write, sandboxes write, workspace read, no secrets, for 90 days. It has Revoke but no Rotate (a new secret would cut the agent off). ### What a key can do [#what-a-key-can-do] **New key** asks for the key's access, and each key's row shows it: * **Full access** (the default, and every key `prized login` makes): everything you can do in the workspace. * **Read only**: it can look at everything you can see and change nothing. * **Custom**: set **Boxes** (boxes, snapshots, environments, shared ports), **Secrets** (secrets and which boxes get them) and **Workspace** (settings, members, team activity, the audit log, billing) each to **None**, **Read**, or **Write** (write includes read). Connecting to a box, and running commands or moving files on one, need Boxes: Write. **Custom** also sets **Sandboxes** ([sandboxes](https://prized.dev/docs/sandboxes) and their templates) to **None**, **Read**, or **Write**; running commands or moving files in a sandbox needs Sandboxes: Write. A key never does more than your role allows; access only narrows it. A key cannot make, rotate or revoke other keys, whatever its access. A call outside its access gets `403 key_access_denied`, with a message naming what the key lacks. To change a key's access, make a new key and revoke the old one. ### When a key expires [#when-a-key-expires] **New key** picks a lifetime: 7, 30 or 90 days (the default), or a year; the workspace owner can also pick **No expiry**. From then on an expired key gets `401 token_expired` on every request, its tunnels end with it, and its SSH key leaves your boxes. A row's light turns amber in the key's last week and red once it has expired; revoke an expired key to clear it from the list. * **Machine keys** from `prized login` expire only after 180 days unused, as above. * **Older keys**, made before keys had lifetimes, never expire and cannot be rotated: replace one with a new key. ### Limit a key to some boxes [#limit-a-key-to-some-boxes] **New key → Boxes → Only some** limits a key to the boxes you tick (up to 50; a contractor ticks only their own). Such a key: * **Can** list, read, wake, pause, resize, move and delete those boxes, connect to them, run commands, move files and prompt agents there, share their ports, and snapshot them. * **Sees nothing else**: any other box answers `404 not_found`, as if it did not exist, and lists leave it out. * **Cannot** reach the whole workspace: creating boxes and forks, secrets, environments, sandboxes, team activity, the audit log and workspace settings answer `403 key_access_denied`. Its access levels still apply, and the SSH certificates it gets open only its boxes. ### Rotate a key [#rotate-a-key] **Rotate** (on a key you made with **New key**) replaces its secret and shows the new one once; the key keeps its name, access, boxes and expiry. The old secret stops working at once and its tunnels close, so replace it wherever it runs right away; to switch with no gap, make a new key, deploy it, then revoke the old one. Only the member who made a key can rotate it. A machine's key is replaced by running `prized login` there, and an expired key cannot be rotated. The old secret cannot obtain a new SSH certificate after rotation. Certificates already issued keep their remaining lifetime. **Revoke** (it asks first) retires one key on its next request: a machine's `prized` stops working until you run `prized login` again, its tunnel closes, and its SSH key leaves your boxes within seconds; a script gets `401`. **Revoke all** does this for every key, yours included, and cancels install commands nobody has run and unfinished [terminal sign-ups](https://prized.dev/docs/terminal-signup); use it for a stolen laptop or a leaked key. A contractor sees only their own keys. The owner can revoke any key (including one from before workspaces existed), anyone else only their own. Revoking touches no boxes, secrets, or balance; to end someone's access, remove them. ## Close or erase the workspace [#close-or-erase-the-workspace] **Close workspace** (owner only, under **Dashboard → Settings → Data and privacy**) pauses every box, cancels the plan immediately, and deletes everything 30 days later unless you reopen; **Erase now** skips the wait. Details, and what is kept afterwards: [Data retention](https://prized.dev/docs/data-retention#closing-a-workspace). --- # Teams > Bring your team in by email or with one link, and what a shared workspace adds to one account: roles, policies that bind members, quiet hours, an audit log with a scheduled export, scoped secrets, and a live activity page. URL: https://prized.dev/docs/teams Every rule here is off until the owner turns it on; none changes a [workspace](https://prized.dev/docs/workspaces) of one. ## Add your team [#add-your-team] 1. Under **Dashboard → Settings → Members**, click **Add member**, type a teammate's email, then click **Send invite**: Prized emails them your invite link. Or copy the link (**Copy** there, or **Copy invite link** on the Boxes page, shown to the owner while they are the only member and have a box; **x** hides it for 90 days) and send it yourself. 2. The link opens a **Join** page for your workspace; signed out, **Sign in to join** takes them through sign-in and back to it. 3. **Join workspace** opens your workspace's Boxes page, with your boxes under **Team**. They create their own with **New box** and [install the CLI](https://prized.dev/docs/quickstart#2-install-the-cli). * **Same company domain:** once you claim it with auto-join on, teammates on it join by signing in, no link needed ([Create one](https://prized.dev/docs/workspaces#create-one)). * **Their own workspace:** unless auto-join brought them in, a teammate's first sign-in also makes them a workspace of their own. Joining keeps it; **Switch workspace** moves between the two. * **The link itself:** replacing it, and what removal does to it, are under [Invite people](https://prized.dev/docs/workspaces#invite-people). ## Roles [#roles] | Role | Can | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Owner | Everything: billing (and its email), the domain and invite link, roles, policies, quiet-hours exemptions, the audit export, removing members. No policy binds the owner; ownership never moves. | | Member | Every box, secret, snapshot and environment; reads the audit log, the activity page and the policies. Policies bind members. | | Contractor | A member fenced to the boxes they own. Policies bind contractors too. | The owner changes a role under **Dashboard → Settings → Members** (a `member.role` audit row). Making a member a contractor revokes the phone passwords, browser tickets and shared-port sessions they hold on boxes they do not own, closes their tunnels to those boxes, and ends the anyone and public links they made ([Share a port](https://prized.dev/docs/ports#share-a-port)). A contractor: * sees only the boxes they created: another member's box, and its snapshots, read as not found everywhere; * cannot access workspace sandboxes (not found). Demotion also blocks pending sandbox changes and monitor-ticket requests that have not yet been accepted; work already accepted may finish. Sandbox requests through API keys use the member's current role, so promotion restores access within the key's permissions. Revoke an API key explicitly when it must remain invalid after a later promotion; * lists environments and upgrades their own boxes, but creating, editing or deleting one is `403 not_allowed`; * always creates **Restricted (safe for third parties)** boxes, which none of your credentials reach ([Safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties)); an API create with `"restricted": false` is `403 policy_denied`, rule `contractor_restricted`; * gets `403 not_allowed` from the audit log, the members list, the policies, workspace secrets and [cloud federation](https://prized.dev/docs/aws), and a secret only when it is [granted](#scoped-secrets) to their box or to them. A box belongs to the member who created it; boxes Prized creates for you belong to the owner (`ownerUserId` on the box object). The dashboard's Boxes page lists the boxes you own first, so a box someone else created for you is in its **Team** section ([Dashboard: Boxes](https://prized.dev/docs/dashboard#boxes)). ## Policies [#policies] **Dashboard → Settings → Policies**: the owner edits, members read, contractors see `Your role does not include the workspace policies.` | Rule | Key | Range | What it does | | ----------------------- | ---------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Spend per member | `memberSpendCapUsd` | $1 to $1,000,000 a cycle, whole dollars | Caps what one member's boxes draw in a billing cycle; checked on create, wake and resize. | | Boxes per member | `memberBoxLimit` | 1 to 100 | Caps a member's live boxes and pending failed-launch cleanup; checked on create, fork and wake. | | Largest size per member | `memberMaxTier` | Any size | The biggest size a member may create or resize to. | | Auto-pause floor | `autoPauseFloorMin` | 30 minutes to 7 days | Members' boxes must auto-pause within this long of going idle; longer or off is refused. | | Quiet hours | `quietHours` | One window a day, per weekday | Pauses every running box in the window ([Quiet hours](#quiet-hours)). | | Audit log retention | `auditRetentionDays` | 7 to 365 days, default 90 | How long [audit rows](#audit-log) are kept. | | Anyone links | `anyoneLinksOwnerOnly` | On or off | Only the owner makes [links anyone can open](https://prized.dev/docs/ports#share-a-port); links made before it was turned on keep working until stopped. Public links are the owner's either way. | * **A refusal** is `403 policy_denied` with `rule` and `message`; the CLI prints the message (exit 5), the dashboard shows it beside the control. * **The spend cycle** is the month ending at your plan's period end, or the UTC calendar month without a plan; spend counts against the box's owner. * **Waking or resizing a teammate's box** checks the box owner's spend, not yours. A wake checks their box count too, refusing only when they are already over the limit. The size cap still applies to every resize a member requests; workspace owners remain exempt from these policies. * **The box limit** refuses a wake only when the member is already over it. A failed launch keeps its slot until cleanup finishes, even after deletion. * **Failed-launch cleanup** also reserves four hours of that box's allowance against the member's spend cap. This is not billed usage; the reservation lifts when cleanup finishes, not when the billing cycle changes. * **Under a floor,** a new box with no auto-pause setting takes the floor, a box from before the floor is paused as if it had it, and a member's **Auto-pause** menu stops there. On the [free credit](https://prized.dev/docs/billing#free-credit) the floor does not apply: only the box's own setting, or the credit's 2-hour default, counts. `GET /api/v1/workspace/policies` reads the rules; changing them is dashboard-only and writes a `policy.update` row ([API reference](https://prized.dev/docs/api#policies)). ## Quiet hours [#quiet-hours] Every box in the workspace, the owner's included, is paused in a daily window: `start` and `end` as `HH:MM`, the weekdays it applies to, the weekdays quiet all day, and one IANA timezone. A window that ends before it starts crosses midnight and belongs to the day it started on. * **A box woken inside the window** stays up 15 minutes, then is paused again. * **To exempt a box,** the owner marks it **Exempt** under **Quiet hours** on its box page (`PATCH /api/v1/boxes/{id}` with `{"quietHoursExempt": true}`; anyone else is `403 not_owner`). A quiet-hours pause is an ordinary [pause](https://prized.dev/docs/boxes#pause-and-resume). ## Audit log [#audit-log] **Dashboard → Settings → Audit log** ([Dashboard: Audit](https://prized.dev/docs/dashboard#audit-log)): who did what, on which box, when. Owners and members read it; contractors see `Your role does not include the audit log.` ### Two origins [#two-origins] * **`server`:** the control plane saw it. * **`client`, shown as reported:** the CLI or the box told us afterwards, because the control plane never sees a command run over ssh, a file copied, or a prompt started. A reported row is a claim by a credential in your workspace, not an observation: an offline or older CLI posts none, and nothing checks it against what ran. Once deletion begins, new client reports for that box are refused; existing rows remain in the log. Every row names the member (`actorUserId`) and the credential that acted (`actorTokenId`); browser terminal and phone sessions count as the member who opened them. **SSH certificate issued** means a credential was issued, not that SSH authentication or a terminal opened successfully. Failed connection attempts can leave these rows; a certificate can also be reused, so the rows are not a count of successful sessions. ### What is recorded [#what-is-recorded] Server-observed kinds record names and hosts, never values, contents, keys or tokens: | Kind | Detail | | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `box.create` | `tier`, `region`, `hostname`, `restricted`, `from` (a snapshot) | | `box.wake`, `box.delete` | | | `box.pause` | `reason`: `quiet_hours`, `auto_pause`, `pause_deadline`, or none for a pause you asked for | | `box.resize`, `box.move_region` | `from`, `to` | | `box.settings` | The keys changed | | `box.env` | `count` | | `box.fork` | `from`, `fromHostname`, `to`, `toHostname`, `tier`, `snapshotId`, `keepSnapshot` | | `box.connect` | **SSH certificate issued**, not a confirmed connection: `keyId`, and `scope` (`terminal`, `monitor`, `mobile-ssh`, `port-share`) for a browser, phone or shared-port session; `monitor` also marks the live charts of the [terminal dashboard](https://prized.dev/docs/cli#terminal-dashboard), which can only read a box's stats | | `box.session` | A browser terminal opened: `scope`, `ticketId` | | `sandbox.create` | Only for a sandbox the key from [agent sign-in](https://prized.dev/docs/agent-sign-in) created: `sandboxId`, `template`, `vcpu`, `memoryMb`, `diskGb`, `tier` (`free`), `fork` (the parent, for a fork's copy), `via` (`agent_signin`) | | `port_share.create`, `port_share.revoke` | A port shared, or the link stopped: `shareId`, `port`, `slug`, `access` (`members`, `anyone`, `public`); `expiresAt` on an anyone or public link; `reason` on a revoke Prized made: `replaced` (shared again with another audience), `member_removed` (its maker left or became a contractor), `staff` ([Share a port](https://prized.dev/docs/ports#share-a-port)) | | `port_share.open` | A member opened a members link (their first visit of the day, or after signing in again), or a visitor first opened an anyone or public link in their browser: `shareId`, `port`, `slug`, `access`; the member is the actor, and a visitor shows as a browser ticket with no member | | `snapshot.create` | `snapshotId`, `name`, `kind` | | `snapshot.delete` | | | `snapshot.template` | Legacy: a template named or cleared, before templates were removed (2026-10-18); no longer written | | `environment.create`, `environment.update`, `environment.delete` | Update carries `change` (the kind of edit) and `key` (the var, file, repo or toggle); never a value or a file body | | `secret.create`, `secret.update`, `secret.delete`, `secret.mount`, `secret.unmount`, `secret.grant`, `secret.revoke` | Names and hosts | | `member.add` | `via`: `first_contact`, `domain`, `invite` | | `member.remove`, `member.leave`, `member.role`, `invite.rotate`, `invite.revoke` | | | `invite.email` | `email`: the address **Add member** sent the invite link to; `unconfirmed: true` when sending could not be confirmed, so the row is not proof it went out | | `token.create` | `name`, `tokenId`, `access`; `expiresAt` and `boxes` (how many) for a key made with **New key** | | `token.revoke` | `tokenId`, or `all: true` with `count` | | `token.rotate` | `tokenId` | | `policy.update` | `keys` | | `command_history.delete` | `commandId`, or `all: true` with `count` ([Command history](https://prized.dev/docs/exec#command-history)) | | `audit.export` | `action` (`set` or `clear`), `host` | | `workspace.settings`, `workspace.retention`, `workspace.reopen` | | | `workspace.close` | `purgeAfter`, `boxesSuspended`, `membersRemoved`, `cliTokensRevoked`, `portSharesRevoked` | Client-reported kinds never record stdout, stderr, file contents, the prompt beyond its first line, or a VNC password: | Kind | Detail | Reported by | | ------------------------------ | ---------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `exec` | `command` (up to 512 characters), `cwd`, `detached`, `processId` | `prized exec` when it runs something; `POST /exec` | | `file.upload`, `file.download` | `path`, `bytes`, `files`, `inline`, `directory` | `prized cp` after a copy that moved something; the edge's file routes | | `prompt` | `firstLine`, `provider`, `runId`, `conversationId`, `queued` | `prized prompt`; `POST /prompts` | | `desktop.start` | `mode`, `profile` | `prized desktop` and `prized browser` (not `--stop`); the dashboard's Desktop tab | ### From the CLI and the API [#from-the-cli-and-the-api] ```bash prized team log # the last 24 hours prized team log --since 7d --kind exec,prompt --member ana@acme.com ``` Columns and flags are on [CLI: Team](https://prized.dev/docs/cli#team). `GET /api/v1/audit` takes the same filters and pages by `before` and `limit`; `format=csv` streams up to 50,000 rows ([API reference](https://prized.dev/docs/api#audit-log)). ### Scheduled export [#scheduled-export] Owners set a URL and a write-only secret in the **Export** section of the Audit page; its status line shows the last delivery and the last error. The copy is yours, at your endpoint; there is no hosted destination. | Part | Contract | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | URL | `https`, up to 2,048 characters, on a public host, with no credentials or fragment. Private, loopback and link-local addresses are refused on save and again before every delivery. | | Secret | 16 to 256 characters, encrypted at rest and never shown again | | Delivery | Once an hour, one `POST` per batch, oldest first: up to 500 events per batch and 20 batches per pass. Ten seconds per request; redirects are never followed. | | Body | `{"v": 1, "workspaceId": "...", "events": [...]}` | | Headers | `Content-Type: application/json`, `X-Prized-Signature: sha256=`, `X-Prized-Event-Id-From`, `X-Prized-Event-Id-To` | | Acknowledging | Any `2xx` advances the cursor; anything else lands on the status line, and the same batch is sent again next hour | | Order | At-least-once and in order, so dedupe on `id`; delivery starts with the rows newer than the moment you saved the destination | ```bash openssl dgst -sha256 -hmac "$SECRET" body.json # must equal the X-Prized-Signature hex ``` ### Retention [#retention] Rows are kept 90 days by default, or 7 to 365 as the owner sets under Policies. A row outlives the API key, member and box it names, and goes when the workspace is erased ([Data retention](https://prized.dev/docs/data-retention#records-retained-after-deletion)). [Zero data retention](https://prized.dev/docs/data-retention#zero-data-retention) does not shorten the window, but with it on the `exec`, `file.upload`, `file.download` and `prompt` rows keep no detail (shown as `no detail kept (zero data retention)`). ## Scoped secrets [#scoped-secrets] A [secret](https://prized.dev/docs/credentials#secrets-for-agents) is workspace-wide until you grant it to a box or a member. | Grants | Who can mount it | | ----------- | -------------------------------------------------------------------------------------------------------------------- | | None | Every box except a contractor's | | One or more | Only the boxes named and the boxes owned by the members named; removing the last grant makes it workspace-wide again | A secret created with an **Available to** choice (or `grants` on the API) starts with those grants; a [connector](https://prized.dev/docs/connectors)'s secret starts granted to the box you connected it to. A box whose grant is revoked loses the value within 15 seconds, and its mount shows **no longer granted** until you unmount it. Grant from the dashboard (**Available to** on the new-secret form, **Details** on a row), the CLI (`prized secrets grant NAME --box BOX` or `--member EMAIL`; [CLI: Secrets](https://prized.dev/docs/cli#secrets)), or the API (`POST /api/v1/secrets/{id}/grants`; [API reference](https://prized.dev/docs/api#secrets)). Mounting a secret the box may not have is `403 secret_not_granted`. ### Usage [#usage] A use is one HTTPS request to an allowlisted host in which the broker swapped in the value, counted whatever the upstream answered; refused plain-HTTP requests, other hosts, and requests without the placeholder do not count. Recorded: the placeholder, the host, the count, and the first and last time; never a path, header, body or value. Counts are a floor (a box restart can lose the last batch); read them under **Details** on the Secrets page, with `prized secrets usage NAME`, or from `GET /api/v1/secrets/{id}/usage`. ## Team activity [#team-activity] **Dashboard → Activity** ([Dashboard: Activity](https://prized.dev/docs/dashboard#activity)) shows what the team is doing now; contractors see their own boxes only. | Term | Meaning | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Attached session | A device (the CLI, a browser terminal, or a phone) connected to a box, counted while its tunnel is open, for at most 12 hours. It says a device is attached, never what is typed or shown. | | Last prompt, last exec | From reported rows, so an older CLI leaves them blank | | Today | Since UTC midnight | | Cycle | The plan's period, or the UTC calendar month without a plan | | Spend | Metered compute attributed to the box that drew it, in whole UTC-hour buckets; attribution, not billing (the workspace pays one bill) | `prized team status` prints the same, and `--json` the object `GET /api/v1/team/status` answers ([CLI: Team](https://prized.dev/docs/cli#team), [API reference](https://prized.dev/docs/api#team)). ## What is not covered [#what-is-not-covered] * **What happens inside an ssh session.** SSH certificate issuance is recorded (`box.connect`), not whether the connection succeeded or the commands typed in it. * **Keys that belong to nobody.** An API key from before workspaces existed is bound by no per-member policy, and its rows name no member ([API keys](https://prized.dev/docs/workspaces#api-keys)). --- # Billing > How a monthly plan ($10 to $1,000, or sized to your boxes past that) becomes credit, what boxes cost by the hour, and what happens when the credit runs out. URL: https://prized.dev/docs/billing ## How it works [#how-it-works] A plan, any rung from $10 to $1,000 a month, lands at least that much credit every cycle; every box meters against it by the second, quoted by the hour: | Box state | Rate | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Awake | Its monthly price spread over a 730 hour month: a Small is $100 a month, so $0.137 an hour. | | Asleep | $0.0001 per GB hour of the disk the box actually has; a Small asleep for a month is about $22. A box that kept a bigger disk pays that rate on the extra disk, asleep or awake. | | [Infinite storage](https://prized.dev/docs/boxes#infinite-storage) | The box bills as its size with that size's disk, awake or asleep, plus $0.05 a GB-month for the space in use beyond that disk: a Small (300 GB) holding 1.3 TB pays $50 a month more. How much each size can hold is on [Boxes](https://prized.dev/docs/boxes#infinite-storage). | * **Pausing keeps the money.** Pause from the dashboard or with [auto-pause](https://prized.dev/docs/boxes#auto-pause); `prized ssh` wakes the box. * **A box that failed to launch costs nothing.** It meters at zero until you delete it; usage from before a later failure still counts. * **Credit rolls over** up to two months of your plan; the excess lapses when a cycle's credit lands, as its own line in your activity. Top-up and free credit are spared that trim for two months after they land. * **Each rung runs one size around the clock:** $10 a Nano, $100 a Small, $1,000 an Extra Large. The credit spends on any mix. * **Long cycles land more credit.** Rates assume 730 hours; a calendar cycle of up to 744 lands proportionally more, at no extra charge. * **A fleet bigger than the biggest rung** gets a plan sized exactly to it: an Extra Large plus a Small is a $1,100/mo plan. ## Free credit [#free-credit] New workspaces get $30 of free credit, once per person, as the owner walks through onboarding or first signs in with `prized login` through the browser; it spends like paid credit, and the tour starts a box on it with no card. It does not refill: at zero, boxes pause ([Running out](#running-out)) and the dashboard opens on the plan picker. Choosing a plan keeps what is left, and the first cycle's credit lands at once. A workspace whose owner already took the free credit, on the same email address or the same Google or GitHub account, gets none, including after [erasing](https://prized.dev/docs/data-retention#closing-a-workspace) the first workspace. An owner who signs up from a [Sandboxes](https://prized.dev/docs/sandboxes#quickstart) link gets the same credit with no tour and no box: the dashboard opens on Sandboxes. While the workspace runs on the free credit alone: | Rule | Detail | | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Nano, Micro, Extra Small, or Small only | Medium and larger need a plan (on the credit alone, the API answers `409 tier_unavailable`): pick the size (and a region) on the tour or the create form, start its plan, and the box launches there once the first cycle's credit lands. A create that names no size gets a Small. Awake around the clock, the credit lasts about three months on a Nano, five weeks on a Micro, nine days on a Small; used eight hours a day and asleep the rest (below), a Small runs about 15 days. | | One box at a time | A second create (from the dashboard, `prized box new`, a restore, or a fork) answers `409 box_limit`; delete the box to free the slot. A plan raises the cap to the usual 100. | | No process at full CPU for more than 30 minutes | One process holding the whole box at full CPU that long pauses the workspace's boxes, and creating or waking one answers `409 workspace_held` with the reason. Builds, tests, agents and single-threaded jobs do not trip it; a long job that fills every core belongs on a plan, and starting one lifts the pause at once. | All three lift the moment a plan is live; a box started on the credit keeps its settings. **A box on the free credit sleeps when nobody is using it.** After 2 hours idle it pauses itself ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)), so the credit goes on the hours you use it: asleep, a box costs only its disk. It never pauses itself in its first 24 hours, while the first sync and setup land. `prized ssh` or `prized up` wakes it, with everything where you left it. Change the window or switch it off per box on its dashboard page or with `prized box auto-pause BOX 4h` (or `off`); a plan lifts the default, and a window you set stays. **Sandboxes on the free credit** run within the [free tier](https://prized.dev/docs/sandboxes#free-tier): up to 4 at once, each up to 2 vCPU, 4 GB of memory and 60 minutes. They draw on the credit at the [sandbox rates](#sandboxes), and the full-CPU rule above holds for them too. A top-up or a plan lifts them to the full quotas. ## Sizes and rates [#sizes-and-rates] The live shape and price table is at https://prized.dev/pricing. The last column is the most a box can cost in a month, and the plan that runs it around the clock. The [pricing page](https://prized.dev/pricing) compares renting the same machine elsewhere. [GPU boxes](https://prized.dev/docs/gpu-boxes) are billed by the hour from the prepaid balance only, never on overage. ## Sandboxes [#sandboxes] [Sandboxes](https://prized.dev/docs/sandboxes) spend the same balance, metered per second with no minimum by the shape they hold: running, by vCPU, memory, and disk; paused, by disk and saved memory only. vCPU: $0.04 per vCPU-hour running; paused, not charged. Memory: $0.0125 per GB-hour running; paused, $0.0001 per gb-hour, for the saved memory. Disk: $0.0001 per GB-hour running; paused, $0.0001 per gb-hour. 1 vCPU, 2 GB, 10 GB disk: $0.066 an hour running, $0.0012 an hour paused. 2 vCPU, 4 GB, 10 GB disk: $0.131 an hour running, $0.0014 an hour paused. 4 vCPU, 8 GB, 10 GB disk (the default): $0.261 an hour running, $0.0018 an hour paused. 8 vCPU, 16 GB, 10 GB disk: $0.521 an hour running, $0.0026 an hour paused. 32 vCPU, 128 GB, 10 GB disk (the largest): $2.881 an hour running, $0.0138 an hour paused. * **Any balance above zero starts them.** On the [free credit](#free-credit) alone they run within the [free tier](https://prized.dev/docs/sandboxes#free-tier); once the workspace has paid in (a plan or a [top-up](#topping-up)), the full quotas apply. An empty balance answers `409 insufficient_balance`. No plan is needed, and the [four-hour check](#starting-and-resizing-boxes) for boxes does not apply. * **Their life is capped.** Every sandbox is deleted at its `maxRuntimeMin`, 24 hours at most, so a forgotten one cannot bill past that ([Automatic pause and delete](https://prized.dev/docs/sandboxes#automatic-pause-and-delete)). * **At zero, they pause.** With [overage](#overage) off, running sandboxes pause when the balance runs out and stay paused until you resume them; with it on, they keep running, as boxes do. * **A sandbox that never came up costs nothing,** like a box that failed to launch. * **Every size bills at the same rates.** A [large sandbox](https://prized.dev/docs/sandboxes#large-sandboxes) pays per vCPU and per GB exactly as a small one does, and nothing for the minute or two it may take to start. ## Choosing and changing the plan [#choosing-and-changing-the-plan] The owner picks a plan on the Billing page, or on the tour's payment step once the [free credit](#free-credit) is spent. Payment is Stripe Checkout, cards only, and the first cycle's credit lands before you are back; with no box yet, choosing a plan starts one at the size that rung runs around the clock, in the region picked with it (Oregon if none). | Change | Effect | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | Upgrade | Starts a fresh cycle today: a full month of the new plan, minus the unused share of the old one, bills now and lands as credit. Your renewal date resets. | | Downgrade | Changes what the next cycle bills, on your existing renewal date; this cycle's credit stays yours. | | Cancel | Stops the plan at the period's end; credit already landed works until spent. Pick a plan again before then and it keeps renewing. | Past $1,000/mo the ladder grows one more rung, sized to your boxes. The balance and plan belong to the [workspace](https://prized.dev/docs/workspaces): everyone spends the same balance and can see the Billing page; only the owner changes the plan, buys top-ups, switches overage, or opens the Stripe portal. ## Topping up [#topping-up] The owner can buy one-time top-ups on the Billing page: $10 to $250 by card, as often as needed, in your balance right away and never changing what the plan bills. With [overage](#overage) on, you need none. **A spent free credit needs no plan.** The plan picker it opens on also offers a one-time top-up of $10, $25 or $50, by card. Paused boxes start again. The [free credit's box limits](#free-credit) stay until you choose a plan, and a top-up leaves each box's auto-pause as it is (2 hours unless you changed it). ## When we tell you [#when-we-tell-you] * **Dashboard banner** when less than two days of credit remain at the current rate; others in overage, after boxes were suspended, while a renewal is being collected or a payment is failing, and before the account is funded. * **`prized status`** prints a credit line when the balance is running low or empty, and says whether boxes keep running (overage, a renewal collecting, or a failed renewal's grace). When the balance has stopped them, it prints the link to the Billing page. * **Reaching for a paused box** says why when the balance is what stopped it: `prized ssh`, `prized up`, plain `ssh `, an editor's remote session and sync print which credit ran out, that nothing was deleted, and the link to the Billing page, instead of a connection error. * **Email** to the workspace's address and every owner, at a quarter and a tenth of what the last cycle left you, and once each on crossing into overage, on boxes pausing, and on a failed payment; at most two reminders if you never started a plan. ## Overage [#overage] **Off by default:** out of credit means boxes pause, and nothing bills past what you put in. The owner can switch it on from the Billing page, and running out then stops nothing: | Case | What happens | | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Usage past zero | Bills at the same hourly rates, as its own line on your next invoice, no markup. Prepaid credit always spends first; new credit (a top-up, an upgrade, the next cycle) pays overage down first. | | You turn it off | The balance is the hard stop again; overage already run up still bills with your next invoice, or a final one if the plan ends first. | | A failed or disputed payment | Switches overage off until a payment collects. A failed renewal still keeps boxes running for seven days ([Failed payments](#failed-payments)). | | A cancelled plan | Its negative balance bills once, on a final invoice; a tail under Stripe's 50 cent minimum is written off. | | A refund, a reversed payment, or a correction | Never charged as overage, even if it makes the balance negative: only metered compute bills as overage. | | A dispute of the payment that settled your overage | Puts the balance, and the overage owed on it, back where they were. | ## Running out [#running-out] With overage off, without a plan, or while a payment is failing past its [grace](#failed-payments), the balance is the hard stop: at zero, boxes suspend. Disks are kept, with your work, installed tools, and git state. Boxes the stop paused start again on their own once the next cycle, an upgrade, a top-up, or overage can run them for four hours; a box you woke, paused, or deleted since is left as you left it. Suspended boxes meter their disk at the sleep rate, and usage the balance cannot cover is written off, never turned into a debt. Until then, anything that tries to reach a box the balance stopped is told so, with the link to pick a plan or add credit ([When we tell you](#when-we-tell-you)). **Renewal day is not running out.** A renewal takes about an hour to collect, so a plan that reaches zero after it renews pauses nothing until the payment goes through, for up to six hours, and that time bills from the new cycle's credit. If the payment fails, the seven-day grace under [Failed payments](#failed-payments) takes over; if the six hours end with neither, your boxes pause. Usage recorded during the wait keeps its running rate and is paid from your next credit, not charged as overage. An unpaid upgrade does not extend this renewal window or delay suspension at zero. If a box stays paused after funds return, resume it yourself. A pause whose intent is uncertain is never undone automatically. > Nothing is deleted automatically. If a balance stays empty for a long time, we contact you before any disk is released. ## Starting and resizing boxes [#starting-and-resizing-boxes] With [overage](#overage) off, a create, a wake, or a resize needs enough balance to run the resulting fleet for four hours, counting every box at the disk it actually has; while a renewal collects or a failed renewal is in its [grace](#failed-payments), a box from the fleet already running when collection began can be woken without that balance unless failed-launch cleanup is still reserving allowance. Waking a previously parked box with credit and pausing it again does not add it to that protected fleet. A box paused before its first start still needs normal funding. It is a check, not a charge: a refusal says what the fleet would burn and what the balance would need. A resize meters at the new rate at once; a box mid-change cannot be resized until it settles. A failed launch still reserves its allowance until its cleanup finishes, even if you delete it. That reservation is not billed, but it still counts against box limits and [member policy limits](https://prized.dev/docs/teams#policies). A failed box cannot be resized. While it is pending and overage is off, even a wake during renewal collection or payment grace needs the full four hours of fleet allowance; add credit or wait for cleanup. A region move keeps the hourly rate but copies the whole disk, so it needs a funded balance ([Regions](https://prized.dev/docs/boxes#regions)). Its caps, and every other, are on [Limits](https://prized.dev/docs/limits). When a new or resized box would cost more around the clock than your plan puts in each month, the owner is offered the covering plan (with no plan yet, a first one). **Resize** offers it as one extra click, quoting the charge due today, and declining just resizes; **Create** pre-selects it, and unchecking creates on your current plan. Nothing charges without the page saying so first, and the prepaid balance is the only thing that gates a box. ## Failed payments [#failed-payments] A failed renewal of a plan that has paid before keeps your boxes running for seven days from the first failure, whatever the balance reads; the banner, the Billing page, `prized status`, and the email name the minute it ends. Overage is off meanwhile, so nothing more goes past zero: the time the boxes run waits, at the rate it ran, for the payment to cover. A box that was running at renewal can be woken again after a pause; a box already asleep before renewal, new boxes, and resizes need enough credit. These wakes still need the [failed-launch cleanup allowance](#starting-and-resizing-boxes). Stripe retries the card on its own schedule; the owner updates it in the Stripe portal, linked from the Billing page, and the charge retries on its own. Paid, nothing changes; still unpaid after seven days, or the plan cancelled, the balance is the hard stop again and boxes pause at zero. Any other failed payment (an upgrade's charge, or a plan's first) suspends nothing by itself: boxes keep running on what remains, and pause at zero. Fully refunded or reversed plan payments do not qualify for renewal grace. A refund of your last qualifying plan payment ends that grace. A refund, or a payment your card issuer pulls back (a dispute), takes the credit it bought out of the balance; a dispute also pauses the plan's billing and switches overage off while it stands. Won, the credit is restored, billing resumes, and overage returns with your next successful payment; lost, the plan is cancelled and overage stays off until you pick a plan again. ## Receipts [#receipts] Every cycle produces a Stripe invoice you can download. The Billing page's recent activity lists each cycle's credit and one usage line per hour; card details, invoices, and receipts are in the Stripe portal, linked there for the owner. ## Custom plans [#custom-plans] A fleet bigger than the largest rung needs no special deal: the plan sizes itself to your boxes ([above](#choosing-and-changing-the-plan)). For a startup deal or a negotiated amount, email the support address shown on https://prized.dev/docs/troubleshooting for a link (it expires after 30 days) the owner opens to subscribe at that amount in one click, or to move an existing plan; it then behaves like any other plan. A custom plan also unlocks [zero data retention](https://prized.dev/docs/data-retention#zero-data-retention). --- # Limits > Every hard cap a box or a workspace can hit, and what the refusal looks like. URL: https://prized.dev/docs/limits Money (the plan range, top-ups, the rollover cap, the free credit) is on [Billing](https://prized.dev/docs/billing). ## Boxes [#boxes] | Limit | Value | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Boxes per workspace | 100 not deleted (paused and deep sleep count); 1 on the [free credit](https://prized.dev/docs/billing#free-credit) | | Balance to create, wake, or resize | Enough to run the whole resulting fleet for 4 hours ([Starting and resizing boxes](https://prized.dev/docs/billing#starting-and-resizing-boxes)); with an empty balance and no overage, nothing starts or wakes ([Running out](https://prized.dev/docs/billing#running-out)) | | Box name | 1 to 32 characters: lowercase letters, digits, and hyphens, starting and ending with a letter or digit; unique in the workspace | | Sizes | 7, Nano to Extra Large ([Machines](https://prized.dev/docs/machines#sizes)); Nano to Small on the [free credit](https://prized.dev/docs/billing#free-credit). No resize while provisioning, resizing, or moving; the disk never shrinks ([Resize](https://prized.dev/docs/boxes#resize)) | | Auto-pause window | 30 minutes to 7 days idle (the dashboard offers up to 24 hours; the CLI and API, the whole range) ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)). Off by default on a plan; 2 hours on the [free credit](https://prized.dev/docs/billing#free-credit), never in a box's first 24 hours. An [auto-pause floor](https://prized.dev/docs/teams#policies) caps members' boxes, except on the free credit | | Pause deadline | 5 minutes to 30 days ahead, on a running box only ([Pause deadline](https://prized.dev/docs/boxes#pause-deadline)) | | Deep sleep | After 14 days paused the disk is parked as a snapshot; waking takes a few minutes ([States](https://prized.dev/docs/boxes#states)) | | Region moves | 1 per box and 3 per workspace in any rolling 24 hours, one at a time, running boxes only; 5 regions. A new box can be created in any region instead ([Regions](https://prized.dev/docs/boxes#regions)) | | Undo after delete | 7 days, as an undo copy under Recently deleted (an infinite storage box's: its files); none for a box deleted within an hour of being created ([Data retention](https://prized.dev/docs/data-retention#deleting-a-box)) | | Capacity | No per-workspace number. With no machine of that size free, a new box waits in `provisioning`, or ends in `provision_failed` with the reason in its events; delete it and create again, or pick another size | ## Sandboxes [#sandboxes] Creating, resuming, or forking a sandbox needs a balance above zero (`409 insufficient_balance` otherwise). On the [free credit](https://prized.dev/docs/billing#free-credit) alone, the [free tier](https://prized.dev/docs/sandboxes#free-tier)'s limits apply instead of the quotas below, and a refusal past one adds `"tier": "free"` and the `limit` it hit; if free-tier access is switched off for your workspace, the API answers `409 paid_balance_required` until it has paid in. When no sandbox capacity is free, a create answers `503 sandbox_capacity` with a `Retry-After` header (seconds) and creates nothing, so nothing is billed; retry after that long. Sandbox CPU and memory limits apply to running sandboxes and paused sandboxes that retain compute capacity for resume. Pausing does not necessarily free quota; delete sandboxes you no longer need, and capacity becomes available after cleanup. This does not change paused billing, which remains storage-only. A resize must fit the workspace's remaining CPU and memory quota. Until it completes, the larger of the old and requested values counts for each resource. Workspace quotas, checked on every create, resume, fork, and resize ([Sandboxes](https://prized.dev/docs/sandboxes)); a refusal is `429 sandbox_quota_exceeded` with the quota in `reason`. Need more? Email the support address shown on https://prized.dev/docs/troubleshooting. | Quota | Default | | -------------- | ----------------------------------------------------- | | Sandboxes | 20 not deleted; paused ones and ones in `error` count | | Running vCPU | 80 across the workspace | | Running memory | 160 GB across the workspace | | Disk | 200 GB across every sandbox not deleted | | Lifetime | 24 hours: `maxRuntimeMin` up to 1,440 | Per sandbox and per request: | Limit | Value | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Shape | 1 to 32 vCPU, 1 to 128 GB of memory, 10 to 128 GB of disk, within the template's bounds (both templates take all of it); more than 16 vCPU or about 58 GB of memory is a [large sandbox](https://prized.dev/docs/sandboxes#large-sandboxes); disk only grows ([Resize](https://prized.dev/docs/sandboxes#resize)) | | Auto-pause and auto-delete | `0` (off) to 40,320 minutes (28 days); `maxRuntimeMin` deletes first | | Fork | 1 to 256 copies a request, as many as fit on the parent's machine: at most 31 of a 1 vCPU sandbox, 7 of a 4 vCPU one; a parent past about 26 GB of memory (about 105 GB for one created as a large sandbox) cannot fork ([Fork](https://prized.dev/docs/sandboxes#fork)); each counts as a create | | Labels | 32, keys up to 64 characters, values up to 256 | | Environment variables | 32 at create, names up to 64 characters, values up to 16 KiB; the whole create body up to 64 KiB | | Command | 300 seconds by default through the API, 3,600 at most; the first 4 MiB of each output stream is kept; the request document up to 1 MiB | | File transfer | 100 MiB and 10 minutes per upload or download, inside `/home/prized` | | Edge requests | 48 concurrent and 60 a second (bursts of 120) per API key; 8 concurrent and 20 a second (bursts of 40) per sandbox; over either answers `429 rate_limited` with `Retry-After` | ## Snapshots [#snapshots] | Limit | Value | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Manual snapshots per workspace | 20; one still being deleted counts until its storage is released. Fork copies, automatic snapshots, and Recently deleted copies never count ([Snapshots](https://prized.dev/docs/snapshots)) | | Snapshot name | 1 to 64 characters of letters, digits, `.`, `_`, and `-`, starting and ending with a letter or digit | | Automatic snapshots per box | `keep` of 1 to 10 (default 5), every 1 to 168 whole hours; each expires after keep x interval | | Snapshots in flight per box | 1 | | Fork disk copy | 8 hours, or 30 minutes without progress; then the new box is marked failed | | Restore target | Any size whose disk holds the snapshot; a smaller disk is refused with the numbers | | Region | A snapshot restores into the region its box was in | | Balance | Any snapshot needs a funded balance or an active plan | ## Environments [#environments] | Limit | Value | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | Environment name | 1 to 64 characters of letters, digits, `.`, `_`, and `-`, starting and ending with a letter or digit ([Environments](https://prized.dev/docs/environments)) | | Env vars per environment | 64, each value a single line of up to 4,096 characters | | Per-box vars | 100 names and 64 KiB of names and values together, inside the launch budget | | Repos per environment | 16, each with an optional setup script of up to 8 KiB | | Secret files per environment | 16, each up to 64 KiB, at a path of up to 256 characters | | Setup script | 16 KiB | | Launch budget | The environment plus the box's own vars must fit 16 KiB compressed; a save or create that would not is refused as `environment_too_large` | | Versions kept | 50 per environment; older ones no live box pins are pruned | ## Commands and files [#commands-and-files] `prized exec`, `prized cp`, the edge API, and the dashboard's Files tab ([Run commands and files](https://prized.dev/docs/exec)). | Limit | Value | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Synchronous run | 600 seconds through the API (default 30); `prized exec` has no limit unless you pass `--timeout` | | Output kept per stream | The last 1 MiB, flagged as truncated | | Exec request document | 1 MiB | | Environment variables per run | 32 | | Inline file read or write | 1 MiB | | Upload or download | 1 GiB and 30 minutes per request; a directory download that reaches the cap is cut off, so a truncated archive never looks complete | | Detached process log tail | 16 KiB by default, 1 MiB at most. The process has no time limit, and its logs are never deleted for you | | Command history | Each record kept 30 days, with the last 1 MiB of each stream and the first 8,192 characters of the command; 10,000 records per box, the oldest going first; 50 MiB of output stored per workspace per hour, past which output is dropped and the stream marked truncated ([Command history](https://prized.dev/docs/exec#command-history)) | | History reads | 200 records a page; 1 MiB of output per request | | Live streams | 4 open per token; each counts as one of the concurrent requests below, and ends with `reconnect` after 10 minutes | | Concurrent requests | 8 per token and 8 per box | | Request rate | 20 per second sustained, bursts of 40, per token and per box; over it answers `429 rate_limited` with `Retry-After` | | JSON request body | 64 KiB on most `/api/v1` routes; 128 KiB on the secrets routes; 2 MiB on the environment routes | | Dashboard Files tab | 1 MiB in the inline editor; 1 GiB per upload or download; one operation at a time per tab; a folder listing shows 2,000 entries and marks itself truncated past that | ## Prompting an agent remotely [#prompting-an-agent-remotely] | Limit | Value | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Prompt | 512 KiB ([Prompt an agent remotely](https://prized.dev/docs/remote-agents)) | | Runs in progress per box | One per 2 GB of memory, from 1 to 16: Nano 1, Micro 2, Extra Small 4, Small 8, Medium and larger 16. Beyond it a new run answers `409 prompt_limit_reached`; `~/.prized/prompts/max-runs` on the box sets another number from 1 to 64 ([Parallel runs](https://prized.dev/docs/remote-agents#parallel-runs-and-the-queue)) | | Runs in progress per conversation | 1; another prompt answers `409 prompt_in_progress` unless it asks to be queued | | Runs listed | 50, newest first; older run directories stay on the box | | Conversations listed | 50, most recently active first | | Events per page | 200 by default, 1,000 at most, or 1 MiB, whichever comes first; one event of any size is always delivered | | Event text | 8 KiB per event (the `raw` line is kept whole); a single event over 32 MiB cannot be delivered | | Requests | As for commands and files above | ## Sync, sessions, and ports [#sync-sessions-and-ports] | Limit | Value | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | File size in a synced project | 256 MB; larger files are skipped and named by `prized sync ls` ([Sync](https://prized.dev/docs/sync#add-a-project)) | | First sync | Up to 3 minutes for the box to install the sync agent and move the first bytes before it is called broken | | Drag-and-drop upload | 512 MB per drop, one file or several; drops older than 7 days are cleaned out ([Sessions](https://prized.dev/docs/sessions#drag-files-in)) | | Mirrored ports | 3000 through 9999 by default; never below 1024, never 22 or 7377; 5900 off by default; TCP only ([Ports](https://prized.dev/docs/ports#limits)) | | Shared port links | One link per port; up to 10 live anyone and public links per workspace, each expiring after 5 minutes to 30 days (7 by default); a link's visitors open it a limited number of times a minute ([Share a port](https://prized.dev/docs/ports#share-a-port)) | ## Desktop [#desktop] | Limit | Value | | -------------------- | -------------------------------------------------------------------- | | Per box | One desktop and one browser window ([Desktop](https://prized.dev/docs/desktop#limits)) | | Screen | 1920x1080, scaled to your window; VNC, no audio, no GPU | | Browser profile name | Letters, digits, `-` and `_`, up to 64 characters | ## Secrets and access [#secrets-and-access] | Limit | Value | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Secrets per workspace | 100 ([Secrets for agents](https://prized.dev/docs/credentials#secrets-for-agents)) | | Secret name and value | The name is `SCREAMING_SNAKE`, a letter first, up to 128 characters; the value up to 16 KiB | | Allowlisted hosts per secret | 32 | | Secret grants | Each names one box or one member ([Scoped secrets](https://prized.dev/docs/teams#scoped-secrets)) | | Secret usage | The usage read returns up to 1,000 rows ([Usage](https://prized.dev/docs/teams#usage)) | | SSH certificates | 15 minutes each, minted for you as you connect | | Phone access password | One per box, no expiry; generating a new one, or revoking, replaces it everywhere ([From your phone](https://prized.dev/docs/codex#from-your-phone)) | | Phone access gateway | 8 connections per box at once; a connection with nothing running on it closes after 15 idle minutes | ## Workspace [#workspace] | Limit | Value | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | API keys | No cap; revoke any from **Dashboard → Settings → API keys** ([API keys](https://prized.dev/docs/workspaces#api-keys)) | | Members | No cap | | Invite link | Does not expire; replaced by **New link** or by removing a member ([Invite people](https://prized.dev/docs/workspaces#invite-people)) | | Invite emails (**Add member**) | 20 an hour per workspace | | Workspace icon | 1 MiB per upload | | Startup deal link | 30 days ([Custom plans](https://prized.dev/docs/billing#custom-plans)) | | Spend per member | Off, or 1 to 1,000,000 whole dollars a billing cycle ([Policies](https://prized.dev/docs/teams#policies)) | | Boxes per member | Off, or 1 to 100 live boxes (running or paused) | | Largest size per member | Off, or any size | | Auto-pause floor | Off, or 30 minutes to 7 days | | Quiet hours | Off, or one `HH:MM` to `HH:MM` window a day in one IANA timezone, per weekday, with all-day days; a box woken inside it stays up 15 minutes ([Quiet hours](https://prized.dev/docs/teams#quiet-hours)) | | Audit log retention | 7 to 365 days, 90 by default ([Audit log](https://prized.dev/docs/teams#audit-log)) | | Audit log reads | 100 rows a page by default, 1,000 at most per request; the CSV carries up to 50,000 rows | | Audit export | An `https` URL of up to 2,048 characters on a public host, and a secret of 16 to 256 characters; 500 events a delivery, 20 deliveries an hourly pass, 10 seconds a request ([Scheduled export](https://prized.dev/docs/teams#scheduled-export)) | | Reported audit detail | 16 keys of up to 40 characters, strings up to 512 characters, a body up to 64 KiB | ## Not allowed [#not-allowed] Cryptocurrency mining, on the free credit or on any plan, in a box or a sandbox. A box or sandbox found mining pauses every box and every sandbox in its workspace. Creating, restoring, forking, waking, resizing, or moving a box, and creating, resuming, or forking a sandbox, then answer `409 workspace_held`; pausing and deleting still work. Nothing is deleted and no credit is taken; paying does not lift it, and neither does deleting the box or sandbox, so email the support address shown on https://prized.dev/docs/troubleshooting for a review. The full list of what is not allowed is the [Acceptable Use Policy](https://prized.dev/acceptable-use), part of the [Terms of Service](https://prized.dev/terms). ## Retention [#retention] | Record | Kept | | ---------------------------- | --------------------------------------- | | Box metrics | 30 days | | Sign-in records | 90 days | | CLI usage aggregates | 90 days | | Audit rows | 90 days by default (7 to 365 by policy) | | Command history | 30 days | | Finished deletion operations | 30 days | The windows and their zero data retention variants are on [Data retention](https://prized.dev/docs/data-retention#telemetry-retention-windows). ## How limits fail [#how-limits-fail] | Where | What you see | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | The API | The HTTP status and an `error` code, with a message that quotes the numbers: `409 box_limit`, `409 insufficient_balance`, `429 snapshot_limit`, `402 secret_limit`, `400 invalid_name`, `400 invalid_auto_pause`, `400 invalid_pause_at`, `400 environment_too_large`, `400 too_many_repos`, `400 too_many_files`, `409 prompt_in_progress`, `413 payload_too_large`, `403 policy_denied` (with `rule` and `message`), `403 secret_not_granted`, `429 rate_limited` with `Retry-After` | | The CLI | The [exit code table](https://prized.dev/docs/cli#for-agents-and-scripts): a value outside a range is `USAGE` (2) before anything is sent; the box count, a taken name, or a balance that cannot run the box is `CONFLICT` (6); the snapshot limit and a refused lifecycle change are `CHECK_FAILED` (5) with the control plane's sentence. `--json` carries the same code and message in `error` | | The dashboard | The same sentence next to the control that asked, or the control is not offered: a size the balance cannot run cannot be picked, and the pause deadline is hidden on a paused box | --- # Data retention > What pausing, deleting, and closing keep and remove, how zero data retention changes that, and what Prized retains afterwards. URL: https://prized.dev/docs/data-retention ## Pause vs delete [#pause-vs-delete] | Action | Machine | Disk | Files, tools, git state | Undo | | --------------- | ---------------------------------- | ------------ | ----------------------- | ------------------------------------------------------------------------------ | | Pause | Stopped, billed at the paused rate | Kept | Kept | Resume any time | | Delete | Terminated | Erased | Gone from the box | Restore the undo copy within 7 days (none for a box deleted in its first hour) | | Close workspace | Every box paused | Kept 30 days | Kept 30 days | Reopen within 30 days | | Erase workspace | Terminated | Erased now | Gone | None | Deep sleep is a pause with the disk moved to a snapshot; deleting a box in deep sleep erases that snapshot and leaves no undo copy. Disks and snapshots are encrypted at rest ([Security and trust](https://prized.dev/docs/security#data-at-rest-and-in-flight)). ## Deleting a box [#deleting-a-box] A deleted box's last state is kept 7 days as a snapshot named `deleted--`, under **Recently deleted** on the **Deleted** tab of **Dashboard → Boxes**; for an [infinite storage](https://prized.dev/docs/boxes#infinite-storage) box the copy is its files, listed as infinite storage with the space it had in use. * **None for a box deleted within an hour of being created**: its contents came from its image, snapshot, or source box. * **Undo a delete** by restoring the copy: a new box with the disk as it was, under the old name if nothing else took it. Restoring renews a disk copy's 7 days. * **An infinite storage box's copy** is its files as stored when the delete began. A box deleted while running gets a few minutes to save what it has cached first; one deleted while paused can miss writes from just before the pause. Restoring makes a new infinite storage box that starts once the files are copied, and does not renew the copy: it goes 7 days after the delete, and its encrypted data is erased from storage within a day after that. * **It does not count** toward the 20 snapshots you can keep. * **Remove it sooner.** **Delete now** on the row removes the copy at once; [zero data retention](#zero-data-retention) removes every copy and stops new ones. A delete removes the machine, its disk, its tunnel and SSH identity, any phone access password, and its [command history](https://prized.dev/docs/exec#command-history) right away; what stays is under [Records retained after deletion](#records-retained-after-deletion). ## Deletion operations [#deletion-operations] Every delete returns an operation you can poll. ```bash curl -X DELETE -H "Authorization: Bearer $PRIZED_API_KEY" https://prized.dev/api/v1/boxes/ # {"box":{...},"operation":{"id":"dop_…","kind":"box_delete","status":"pending",...}} curl -H "Authorization: Bearer $PRIZED_API_KEY" https://prized.dev/api/v1/operations/dop_… # {"operation":{"id":"dop_…","kind":"box_delete","status":"completed","completedAt":"…"}} ``` `status` is `pending`, `processing`, `completed` (for a box, once it reads `terminated`), or `failed` with `error` (rare, and worth a support message). `DELETE /api/v1/snapshots/` answers the same way with `kind: "snapshot_delete"`. Deleting something already being deleted returns the same operation, and finished operations stay readable for 30 days. From the CLI, `prized ops status ` prints one and `--wait` polls it. Every route is on the [API reference](https://prized.dev/docs/api). ## Zero data retention [#zero-data-retention] **Dashboard → Settings → Data and privacy**, owner only (no API key can switch it), on [custom plans](https://prized.dev/docs/billing#custom-plans). On a self-serve rung or the free credit, **Talk to us** (a short form we answer by email) replaces the switch, and the API answers `403 plan_required`. Turning it on asks you to type exactly `delete deleted box data`; turning it off asks nothing and works on any plan. With it on: * A deleted box leaves no undo copy, so a delete cannot be undone; earlier copies are removed at once. Prized's disaster-recovery backups are not affected and each ages out within a week (see [below](#records-retained-after-deletion)). An [infinite storage](https://prized.dev/docs/boxes#infinite-storage) box's files are gone from the box and from your workspace at once; their encrypted data is erased from storage within 8 days. * Metrics are kept 24 hours instead of 30 days, and the Metrics page stores no process or port history (what it stored is deleted). * Snapshots you take yourself are untouched. * The [audit log](https://prized.dev/docs/teams#audit-log) keeps every row for its full window, but `exec`, `file.upload`, `file.download`, and `prompt` rows are written with no detail (`no detail kept (zero data retention)`). * [Command history](https://prized.dev/docs/exec#command-history) keeps that a command ran, who ran it, when, how it ended, and how many bytes it printed, but no command line, directory, or output; turning it on deletes those from existing records. ## Closing a workspace [#closing-a-workspace] **Dashboard → Settings → Data and privacy → Close workspace**, owner only, confirmed by typing the workspace's name. At once: * The plan is canceled. Credit in the balance stays but is not refunded. * Every box is paused; disks are kept. * Every API key, install link, and phone access password is revoked. Your own dashboard sign-in stays, so invoices stay readable and you can reopen. * Other members are removed, the invite link stops working, and domain auto-join is switched off. * You get one email naming the deletion date. The workspace is kept for 30 days; a banner names the date. Sandboxes still end on their normal schedule, and records with their own window below still age out. **Reopen** clears the close: boxes stay paused until you start them, you pick a plan again, and auto-join stays off until you switch it back on. Until you reopen, no box can start or wake and no plan can be bought or changed. **Erase now**, below Close, skips the window: it terminates every box and disk; deletes every snapshot, secret, environment, and token, the audit log, and policies; cancels the plan (any overage owed bills on a final invoice, as for any [cancelled plan](https://prized.dev/docs/billing#overage)); removes the billing account from Stripe; and signs you out for good, unless you are also a member of another workspace, which you stay signed in to. There is no undo. You can sign up again with the same address right away: you get a new workspace, or land in another workspace you still belong to. [Free credit](https://prized.dev/docs/billing#free-credit) already taken is not granted again. ## Records retained after deletion [#records-retained-after-deletion] Deleting a box or a snapshot removes the machine, the disk, and the storage; the box's event history, its ledger usage, and the [audit rows](https://prized.dev/docs/teams#audit-log) that name it stay in the workspace until the workspace is erased. Erasing the workspace (**Erase now**, or 30 days after a close) deletes everything in it: boxes, disks, snapshots, secrets, environments, tokens, members, the ledger, hourly usage, event history, command history, audit log, policies, and CLI usage aggregates. What remains: * **Invoices and payment records at Stripe**, as required for tax and accounting. Prized keeps no copy of your ledger. * **Sign-in identity history**: which provider account (Google or GitHub) signed in as which user, so a reassigned or recreated mailbox cannot inherit a deleted account's standing. * **Free credit record**: one-way hashes of the email addresses and Google or GitHub accounts that took the [free credit](https://prized.dev/docs/billing#free-credit), and of any later ones that signed in alongside them, so signing up again does not grant it twice. The addresses themselves are not kept. * **Abuse records**: if a computer signed in to several new workspaces, its key fingerprint and those workspaces' identifiers. The 90 days run from when we last added a workspace to that computer's record, which we do when the pattern is detected (within minutes of the use that completes it; the uses it counts can be up to a day older), and the record is deleted in the hourly cleanup after that. Adding a workspace restarts the 90 days for the whole record, including workspaces erased since. A workspace held because of it keeps the same data in its hold record for 90 days after the latest use of that computer's sign-ins the hold counted, also deleted in the hourly cleanup after that. * **Fleet-level aggregate telemetry**: counts describing the fleet as a whole, with no per-customer identity. * **Disaster-recovery backups of box disks**, for up to a week after the disk is gone: a rolling week of encrypted backups of every box disk, kept to recover from a platform failure. You cannot see them, and no customer action removes them early. * **Backups of account data**, for about 65 days after the erase: encrypted backups kept to recover from a platform failure. You cannot see them, and no customer action removes them early. ## Telemetry retention windows [#telemetry-retention-windows] | Data | Kept | With zero data retention | | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | Box metrics (CPU, memory, disk, network) | 30 days | 24 hours | | Process and listening-port snapshots (pinning a moment on the Metrics page) | 72 hours | Not stored | | CLI usage aggregates (command names, versions, exit codes) | 90 days | 90 days | | Sign-in records (time, network address, browser) | 90 days | 90 days | | Abuse records for a computer that signed in to several new workspaces (a one-way fingerprint of its SSH public key, the workspace identifiers) | 90 days after we last added a workspace to the record, deleted in the next hourly cleanup (a hold record's copy: 90 days after the latest use the hold counted) | Same | | Audit log ([Teams](https://prized.dev/docs/teams#retention)) | 90 days by default, 7 to 365 by policy | Same; the detail of exec, file, and prompt rows is not kept | | Command history (what `prized exec` and the edge ran, with up to the last 1 MiB of each output stream) | 30 days, or until you delete the record or the box | 30 days; no command line, directory, or output | --- # CLI reference > Every prized command, flag, JSON shape, and exit code, on macOS, Linux, and Windows. URL: https://prized.dev/docs/cli ## Commands [#commands] Add `--help` to any command. With `--json`, every non-interactive command prints exactly one object, `{"ok": true, ...}` with the payload inline on success; failures and exit codes are under [For agents and scripts](#for-agents-and-scripts). `prized help` lists every command with a line each, and `prized help ` (or `prized --help`) prints one command's flags and examples. ### Sign in and set up [#sign-in-and-set-up] `prized login` opens the dashboard to make an API key for this machine, stores it in `~/.config/prized` (`%LOCALAPPDATA%\Prized` on Windows), sets up your SSH identity, and registers this machine's device key ([Claude Desktop remote](https://prized.dev/docs/claude-desktop)); with no config yet and exactly one box on the account, it also connects that box. With no box, it points at `prized box new`, then `prized setup` in a project folder. Signing in this way, through the browser, also gives a new workspace's owner the [free credit](https://prized.dev/docs/billing#free-credit); signing in with `--token` does not. Flags: `--token` (an API key from the dashboard or a single-use install token; the only form `--json` accepts), `--token-stdin`, `--url`. JSON: `{"ok": true, "email", "workspace", "boxes", "box", "url"}`, plus `keyExpiresAt` for a dashboard key that expires. The key `prized login` makes never expires while the machine uses it; after 180 days unused, commands exit 8 with `this API key has expired` and you sign in again ([When a key expires](https://prized.dev/docs/workspaces#when-a-key-expires)). With no box, login also names `prized sandbox create`: a sandbox needs no box. Your saved sign-in is tied to the control-plane address you authenticated with. Changing `[controlplane].url`, `--config`, or `PRIZED_CONFIG` does not authorize sending it to another address. If the CLI reports a different control-plane origin, check the URL before signing in there; older saved sign-ins work only with Prized's production service until you sign in again. `prized start` is what [`npx prized`](https://prized.dev/docs/terminal-signup) runs: the sign-up in this terminal, a box, where you work, and this machine set up. Already signed in, it points you to `prized setup`. `prized setup [PATH]` sets this machine up for your box and is safe to re-run: a repairing health check, the project to sync, your environment, the agents, and a detached session. `prized setup auto` skips the auto-or-manual question ([Quickstart](https://prized.dev/docs/quickstart#4-set-up)). | Flag | Meaning | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `--project`, `--no-project` | The project to sync (a name or a path), or none. | | `--copy`, `--copy-once`, `--clone` | Copy and keep syncing, copy once with no sync, or `git clone` on the box; the default syncs in place. | | `--merge` | Merge into an existing folder on the box instead of refusing. | | `--env`, `--env-secrets`, `--env-data`, `--no-env` | The [environment](https://prized.dev/docs/environment) groups to bring; data is never covered by auto or `-y`. | | `--env-workers N` | Environment items installed at once (default 8); `1` installs them in order. | | `--agent NAME`, `--no-agents`, `--save` | Agents to hand off (`claude`, `codex`, `cursor`, `chatgpt`); `--save` records the pick. | | `--gh`, `--no-gh`, `--install-cursor` | The GitHub token and git identity; Cursor's installer. | | `--start`, `--no-start`, `--attach`, `--no-attach`, `--session NAME` | The picked agent's detached session; `--attach` opens it here instead of printing the reattach command. | | `--box NAME` | The box to set up; one other than the default gets its own config and daemon ([Several boxes](https://prized.dev/docs/boxes#several-boxes)). | | `--dry-run`, `--non-interactive` | Print what a run would do and change nothing; never prompt. | | `--signup ID` | Finish what a [terminal sign-up](https://prized.dev/docs/terminal-signup) started (its box, mode, and app); `npx prized` passes it. | JSON: `{"ok": true, "steps": [{"id", "status": "done|skipped|failed", "duration"}], "state": "READY|DEGRADED", "env"}`. `prized init` adopts a box on your account (config, SSH stanza, daemon); `login` and `setup` run it for you. Flags: `--box NAME` (another box, with its own `boxes/.toml` and daemon), `--force` (make it the default instead, rewriting `config.toml` after a backup; refused for a box with its own file), `--user NAME`, `--no-daemon`, `--non-interactive`. `prized env scan|plan|migrate` runs the environment step alone: list what your machine has, print the questions, apply them. Flags: `--project PATH`, `--advisor`/`--no-advisor`, `--non-interactive` ([Bring your environment](https://prized.dev/docs/environment)). ### Box lifecycle [#box-lifecycle] Every action also works from the [dashboard](https://prized.dev/dashboard); what each does is on [Boxes](https://prized.dev/docs/boxes). * **Naming a box.** `BOX` is a hostname (`mango`), a box id, `current` (the last box this machine created), or `self` (the box the command runs on); omitted, it is the box you set up. An unknown name exits 5 and lists your boxes. * **Sizes.** Tier id or public name: `nano` (Nano), `micro` (Micro), `lite` (Extra Small), `flow` (Small), `pro` (Medium), `max` (Large), `ultra` (Extra Large); prices on [Billing](https://prized.dev/docs/billing). Until a plan is live, the [free credit](https://prized.dev/docs/billing#free-credit) allows Nano through Small, one box at a time: a bigger size exits 5, a second box exits 6. * **Windows.** `--ttl` and `--auto-pause` take a duration with a unit: `45m`, `4h`, `1h30m`, `2d`. #### box new [#box-new] ```bash prized box new # a fruit name, the size your balance runs comfortably prized box new mango --size Small --ttl 4h --auto-pause 45m --wait ``` Returns as soon as the control plane has the box, which becomes `current`; boot times are on [Boxes](https://prized.dev/docs/boxes). | Flag | Meaning | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `NAME` | Lowercase letters, digits, and hyphens, starting and ending with a letter or digit, up to 32 characters. | | `--tier`, `--size` | The size; either name works. | | `--ttl WINDOW` | A [pause deadline](https://prized.dev/docs/boxes#pause-deadline), 5m to 30d from now. | | `--auto-pause WINDOW\|off` | [Idle auto-pause](https://prized.dev/docs/boxes#auto-pause), 30m to 7d, or `off`; if absent, off on a plan and 2h on the [free credit](https://prized.dev/docs/billing#free-credit). | | `--environment NAME` | Apply a saved [environment](https://prized.dev/docs/boxes#environments) at first boot, by name or `env_` id. | | `--from-snapshot ID` | Restore a [snapshot](https://prized.dev/docs/boxes#snapshots) into the new box, at the snapshot's size. | | `--region REGION` | Where the box starts, by AWS id or name ([Regions](https://prized.dev/docs/machines#regions)); `us-west-2` (Oregon) by default. With `--from-snapshot`, a [move](https://prized.dev/docs/boxes#regions) after first boot. | | `--storage TYPE` | `infinite` keeps the box's files in [infinite storage](https://prized.dev/docs/boxes#infinite-storage) (Extra Small and up, in a region that offers it, currently `us-west-2`, not from a snapshot); `standard` is the box's own disk, the default (in a workspace in the infinite storage alpha, a box that can have infinite storage gets it unless you pass `standard`); a control plane without infinite storage fails the create, naming the box it made. | | `--wait` | Poll until the box is running (5 minute budget), then print the `prized ssh` line. | Exit 6 (`CONFLICT`): a taken name, the box count, or a balance that cannot run the box. Exit 5: a size not on your plan, a bad name, a window out of bounds, or infinite storage the box cannot have. #### The rest of the box group [#the-rest-of-the-box-group] Each prints `{"ok": true, "box": {...}}` with [the box object](#the-box-object) unless noted. | Command | What it does | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `box ls [--all] [--filter STATES] [--names]` | NAME, STATE, SIZE, REGION, AUTO-PAUSE, PAUSE-AT, CREATED; `--all` adds terminated boxes. JSON: `{"boxes"}`. `--names` prints only a bare JSON array of names, like `["mango"]` (`[]` when none), even with `-q`; it replaces `--json` (both together exit 2). | | `box info BOX` | State, size, region, tunnel IP, login user, disk, pause settings, last vitals, last eight events. JSON adds `events` and `pausesSurvived`. | | `box pause BOX`, `box resume BOX [--wait]` | Aliases `stop`, `wake` ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)); `--wait` polls until running. Idempotent; a resume the balance cannot cover exits 5. | | `box delete BOX [-y] [--missing-ok]` | Permanent: machine and disk gone. Asks you to type the name; `--json` needs `-y` (exit 2 without). `--missing-ok` exits 0 with a note on stderr when the box is not on your account or already deleted; JSON is then `{"ok": true, "missing": true}`, with no `box`. | | `box rebind BOX` | Binds a name's ssh alias to the box that has the name now. After a box is recreated or its freed name taken, `ssh BOX` and the prized commands refuse the name until you rebind. | | `box resize BOX SIZE` (or `prized resize SIZE`) | Restarts the box at the new size ([Resize](https://prized.dev/docs/boxes#resize)). Refusals exit 5. JSON adds `from`, `to`. | | `box move BOX REGION` | Moves the box ([Regions](https://prized.dev/docs/boxes#regions)); `desiredRegion` is set at once, `region` changes when the move lands. | | `box extend BOX --ttl WINDOW \| --no-deadline` | Sets, replaces, or clears the [pause deadline](https://prized.dev/docs/boxes#pause-deadline); setting one needs a running box (exit 5), clearing always works. | | `box auto-pause BOX WINDOW\|off` | Sets the [idle window](https://prized.dev/docs/boxes#auto-pause) (30m to 7d, or bare minutes) or switches it off. | | `box auto-snapshot BOX --every 6h\|off [--keep 5]` | Sets or clears a [snapshot schedule](https://prized.dev/docs/snapshots#automatic-snapshots): every 1 to 168 hours, keeping 1 to 10. | #### The box object [#the-box-object] ```json { "id": "5d0a…", "hostname": "mango", "observedState": "running", "desiredState": "running", "tier": "flow", "instanceType": "m6a.xlarge", "region": "us-west-2", "desiredRegion": "us-west-2", "loginUser": "hudson", "boxIp": "10.77.3.1", "diskGb": 100, "autoPauseMin": 45, "pauseAt": "2026-09-01T18:30:00Z", "lastHeartbeatAt": "2026-09-01T15:17:52Z", "createdAt": "2026-08-29T12:00:00Z", "runningSince": "2026-09-01T09:02:11Z", "vitals": {"cpu_pct": 12, "mem_pct": 41, "disk_pct": 63, "idle_for_sec": 720} } ``` `autoPauseMin`, `pauseAt`, `diskGb`, `lastHeartbeatAt`, and `vitals` are `null` when unset or unknown. `runningSince` is when the box last started, resumed or rebooted; it is `null` unless the box is running or degraded, and `box ls --json` and `box info --json` carry it. Timestamps are RFC 3339 UTC. #### Terminal dashboard [#terminal-dashboard] ```bash prized dashboard ``` A full-screen view of your boxes in the terminal (aliases `dash`, `top`; `prized dashboard BOX` selects that box first): every box with its state and vitals (yours first, your teammates' folded under Team) and, for the one you select, live CPU, memory and network charts, its processes, ports, sessions, disks and recent events. A paused box shows its history and is never woken by selecting it. Click a box, a tab, or a button at the bottom, or use the keys: | Key | What it does | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `↑` `↓` (or `j` `k`) | Select a box | | `Enter` (or `s`, or double-click) | Open a shell on the box (`prized ssh`); on the Sessions tab, attach the selected session. A paused box asks before it wakes. Exit the shell to come back | | `p`, `r` | Pause (after you confirm) or resume the box | | `o` | Open the box in the web dashboard | | `1` `2` `3` | Chart history: 15 minutes, 1 hour, 24 hours | | `/` | Filter boxes by name | | `t` | Show or hide your teammates' boxes | | `?` | Every key | | `q` | Quit | Shell actions stay bound to the selected box’s ID, including after a wake confirmation. If that box was deleted and another box reuses its name, the shell is refused instead of connecting to the replacement. Refresh the fleet and deliberately select the box you want. It needs a terminal at least 60 columns by 15 rows; `box ls` is the plain list for scripts. ### Sandboxes [#sandboxes] `prized sandbox` (alias `prized sbx`) runs [Sandboxes](https://prized.dev/docs/sandboxes) (beta) from the terminal: short-lived machines your code and agents create, billed per second. It needs `prized` v0.21.0 or later (`prized update` upgrades an older one). On a new machine, install it, then open a new terminal so `prized` is on your `PATH` (Windows: [install line](https://prized.dev/docs/quickstart#2-install-the-cli)): ```bash curl -fsSL https://prized.dev/install.sh | sh ``` Every command uses the API key in `PRIZED_API_KEY` when it is set, so CI and agents need no login, and your `prized login` otherwise. Get a key from the [Sandboxes quickstart](https://prized.dev/docs/sandboxes#quickstart). ```bash ID=$(prized sandbox create --template prized-agent-v1) prized sandbox exec $ID -- "python3 -c 'print(6 * 7)'" prized sandbox rm $ID ``` | Command | What it does | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sandbox create [--template ID] [--vcpu N] [--memory GB] [--disk GB] [--env K=V] [--label K=V] [--max-runtime W] [--auto-pause W] [--auto-delete W] [--no-wait]` | Creates a sandbox from `prized-base-v1` unless `--template` names another, waits until it is running and answers commands, then prints its id alone on stdout (progress goes to stderr). `--memory` is in GB, or MB with `m` (`1536m`); the shape must fit the template's range (`sandbox templates`). `--env` variables reach every command. `--max-runtime` is its whole lifetime, `--auto-pause` pauses it after that long with no command or file copy, `--auto-delete` deletes it after that long paused. Windows take a unit (`90m`, `2h`, `1d`) or bare minutes; `off` turns `--auto-pause` or `--auto-delete` off. `--no-wait` prints the id at once. Ctrl-C before it prints `creating ID` deletes the sandbox it had started, and says so (or names one it could not delete); after, the sandbox stays (`sandbox rm ID`). | | `sandbox ls` | ID, STATE, TEMPLATE, VCPU, MEMORY, DISK, LABELS, CREATED, EXPIRES; deleted sandboxes are left out. JSON: `{"sandboxes"}`. | | `sandbox get ID` | One sandbox: state, template, shape, labels, lifetime, and timers. | | `sandbox exec ID [--cwd DIR] [--env K=V] [--timeout D] -- CMD...` | Runs one command under a shell in the workspace (`/home/prized/work`; a relative `--cwd` is under it) and exits with its code. The words after `--` are joined into one shell command, as ssh does, so quote what that shell should see. stdout and stderr arrive when the command ends, up to 4 MB each. `--timeout` is the command's limit, 5m unless set and 1h at most; a command that runs out exits 124. JSON: `{"sandboxId", "exitCode", "stdout", "stderr", "durationMs", "timedOut"}`. | | `sandbox cp SRC DST` | Copies one file of up to 100 MB; the sandbox side is `ID:PATH`, relative to the workspace, or `~/` for the home. A directory as DST takes the file under its own name; `-` is stdin or stdout. JSON: `{"sandboxId", "direction", "from", "to", "bytes"}`. | | `sandbox pause ID [--wait]` | Keeps memory, processes, and disk, and bills storage only. | | `sandbox resume ID [--no-wait]` | Waits until it answers commands again. `exec` and `cp` resume a paused sandbox on their own. | | `sandbox fork ID [--count N] [--label K=V] [--no-wait]` | N live copies of a running sandbox, files, memory, and processes included; prints their ids, one per line, once each is running. JSON: `{"fork", "sandboxes"}`. | | `sandbox rm ID...` | Deletes sandboxes for good, with no prompt. JSON: `{"sandboxes"}`, the ones deleted. | | `sandbox templates` | Each template's default shape, the range `create` accepts, and its default lifetime. JSON: `{"templates"}`. | `create`, `get`, `pause`, and `resume` print `{"ok": true, "sandbox": {...}}`: ```json { "id": "sbx_3f9c0a1b2d4e5f60", "state": "running", "template": "prized-base-v1", "resources": {"vcpu": 2, "memoryMb": 4096, "diskGb": 10}, "labels": {"job": "ci-42"}, "autoPauseMin": 10, "autoDeleteMin": null, "expiresAt": "2026-10-14T10:00:00Z", "createdAt": "2026-10-13T10:00:00Z", "lastActiveAt": null, "errorReason": null, "toolboxBase": "https://term.prized.dev/v1/sandbox/sbx_3f9c0a1b2d4e5f60" } ``` `state` is one of `starting`, `running`, `pausing`, `paused`, `resizing`, `deleting`, `deleted`, `error`. Exit 6 (`CONFLICT`): a sandbox limit or an empty balance; on free credit a limit is the free tier's, and the message says so and links the top-up that raises it. Exit 5: a workspace without sandboxes, an unknown template or id, a shape outside the template's range, a workspace on hold, or a refused fork. Exit 8 (`AUTH`): no login and no `PRIZED_API_KEY`, or the key was rejected or has expired. A set `PRIZED_API_KEY` is used instead of your login, so when it is the one rejected, replace it or `unset PRIZED_API_KEY`; `prized login` alone does not help. ### Snapshots [#snapshots] | Command | What it does | | ------------------------------------------------------------------------- | ---------------------------------------------- | | `prized snapshot create BOX [--name NAME] [--description TEXT]` | Snapshot a disk without stopping the box. | | `snapshot ls [--box BOX] [--kind KIND]` | List snapshots. | | `snapshot restore SNAPSHOT [--name NAME] [--tier SHAPE]` | Launch a new box from a snapshot. | | `snapshot delete SNAPSHOT` | Retire a snapshot. | | `prized fork BOX [--name NAME] [--tier SHAPE] [--keep-snapshot] [--wait]` | A new box from a copy of a running box's disk. | Behaviour, kinds, and limits: [Snapshots](https://prized.dev/docs/snapshots). ### Environments [#environments] `prized environments` (alias `envs`) manages what a new box inherits: `ls`, `info`, `new`, `default`, `rm`, `set`, `set-var`/`rm-var`, `set-file`/`rm-file`, `add-repo`/`rm-repo`, `upgrade`. Every command and its JSON: [Environments](https://prized.dev/docs/environments). ### Secrets [#secrets] `prized secrets` controls who may use a workspace secret and shows what it was spent on; values are set and rotated in the dashboard only ([Scoped secrets](https://prized.dev/docs/teams#scoped-secrets)). | Command | What it does | | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `secrets ls [--box BOX]` | NAME, SCOPE, HOSTS, MOUNTS, ROTATED; with `--box`, only what that box may mount. | | `secrets grant NAME --box BOX \| --member EMAIL` | Make the secret available to one box, or to every box a member owns; the first grant narrows a workspace-wide secret to its grants. | | `secrets revoke NAME --box BOX \| --member EMAIL` | Take a grant back; the box loses the secret within seconds. Revoking the last grant makes the secret workspace-wide again. | | `secrets grants NAME` | Who has it: KIND, TO, SINCE. | | `secrets usage NAME` | Which boxes spent it against which hosts: BOX, HOST, FIRST, LAST, COUNT (a floor). | `grant` and `revoke` take exactly one of `--box` and `--member` (exit 2 otherwise); an unknown secret, box, member, or grant exits 5; a `409` exits 6. JSON mirrors the columns. ### Connect [#connect] `prized ssh [BOX] [SESSION] [-- CMD...]` attaches to a persistent tmux session (waking a suspended box) and forwards a non-default box's ports while connected ([Sessions](https://prized.dev/docs/sessions)). Flags: `-s, --session NAME` (default `main`), `--ls`, `--kill NAME` (asks unless `-y`), `--raw` (no tmux), `--no-forward`, `--no-drop-upload`, `--no-clipboard` ([Copy out](https://prized.dev/docs/sessions#copy-out)). With `-- CMD` it runs that one command instead, sharing the box's connection the way `prized exec` does. Interactive forms reject `--json` (exit 2); once connected, the exit code is ssh's or the remote command's. JSON: `--ls` `{"sessions": [{"name", "windows", "created", "attached"}]}`, `--kill` `{"killed"}`. Every command that uses SSH runs the OpenSSH client from the system directories (`/usr/bin`, or `System32\OpenSSH` on Windows), never one on `PATH`; name another with `bin = "/path/to/ssh"` under `[ssh]` in the config. `prized mosh [BOX] [SESSION]` is the same session over mosh (installed locally; not on Windows yet), with `-s`, `--raw`, `--no-forward`, `--no-drop-upload`. `prized herdr [BOX] [SESSION]` lands in [herdr](https://prized.dev/docs/herdr) instead of tmux, installing it on the box the first time. `prized up` starts the box, reconnects everything from cold, and prints the `status` block; `--resume` resumes paused syncs, `--wait-timeout` (default `5m`) bounds the wait. `prized proxy BOX PORT[:LOCALPORT]` forwards one box port to localhost until interrupted. ### Run commands and files [#run-commands-and-files] `prized exec [BOX] [flags] -- CMD...` runs one command on the box and exits with its code: `--cwd`, `--timeout`, and `--env K=V` shape the run, `--detach` returns an id, and `--status ID`, `--ps`, `--kill ID` inspect or stop detached runs without waking a paused box. Runs back to back share one connection to the box, so after the first, each one starts without a new handshake. JSON: `{"ok": true, "exitCode", "stdout", "stderr", "commandId"}`. `prized exec history [BOX]` lists what ran and `prized exec logs [BOX] ID [--stderr] [--offset N] [--follow]` prints one command's output, from Prized rather than the box, so both work while it is paused ([Command history](https://prized.dev/docs/exec#command-history)); if the live-log address is not trusted, `--follow` says so and prints retained history instead. Terminal display neutralizes control characters while file and pipe exports keep exact bytes. `--delete ID` and `--delete-all` remove records. `prized cp [-r] SRC DST` copies files scp-style: `box:path` is a path on a box, `:path` one on your box. Both are on [Run commands and files](https://prized.dev/docs/exec), and both report what they did (never output or contents) to the [audit log](https://prized.dev/docs/teams#audit-log) as a **reported** row. ### Desktop [#desktop] `prized desktop [BOX]` opens the box's desktop in the dashboard and forwards its VNC port until interrupted; `--no-open` prints the `vnc://` address and password, `--stop` ends it. `prized browser [BOX]` does the same with one Chrome window (`--profile`, `--url`). Starting either reports a `desktop.start` audit row (mode and profile, never the password); see [Desktop](https://prized.dev/docs/desktop). ### Sync [#sync] `prized sync add PROJECT | add --local PATH` mirrors a folder between the box and this machine ([Sync](https://prized.dev/docs/sync)). Flags: `--local PATH` (your own folder, in place, instead of `~/Prized/`), `--remote PATH` (default `~/code/`), `--name NAME`, `--prefer box|mac` (default `box`), `--mode MODE` (default `two-way-resolved`), `--ignore` and `--unignore`, `--create` (make the remote folder), `--paused`, `--merge` (allow a non-empty local folder). `prized sync` (or `sync ls`) lists sessions with state and problems; `pause`, `resume`, and `flush` take a name or `--all` (`flush --timeout`, default `60s`); `repair NAME` recreates a halted session; `rm NAME` removes one (`--delete-local`, `--delete-box` also move the files aside). JSON: `add` `{"project"}`; `ls` `{"projects", "degraded"?}`; `repair` `{"repaired"}`; `rm` `{"removed", "files": "keep|trash_local|trash_box"}`. ### Mount [#mount] `prized mount [BOX]` mounts a box in Finder from a background process and returns, macOS only ([Mount](https://prized.dev/docs/mount)); `-f, --foreground` serves from the terminal until interrupted. Flags: `--dir PATH` (default the home directory), `--at PATH` (default `~/Prized/boxes/`), `--read-only`, `--mount-opt OPTS`, `--no-open`. `prized unmount [BOX]` stops a mount, background or foreground. JSON: mount `{"box", "mountpoint", "pid", "detached", "log"}`, plus `"already": true` for a box already mounted (the call then opens the folder); unmount `{"box", "mountpoint", "unmounted"}`. ### Ports [#ports] `prized ports ls` lists the box's listeners and their mirror state; `prized ports toggle PORT` switches one mirror on or off ([Ports](https://prized.dev/docs/ports)). JSON: `ls` `{"ports": [{"port", "label", "process", "state", "reason"}]}`; `toggle` `{"port", "state", "reason"}`. `prized ports share PORT` prints a link sharing the port with your workspace (the same link again for a shared port); `--anyone` makes a link anyone who has it can open, shown once, and `--public` one anyone at all can open (owner only); `--expires 7d` sets how long either lasts, 5 minutes to 30 days, 7 days by default. `--anyone` or `--public` on a port shared with another audience replaces its link; with neither flag, a shared port prints its existing link. `prized ports share` alone lists the box's shared ports; `prized ports unshare PORT` stops sharing ([Share a port](https://prized.dev/docs/ports#share-a-port)). A bad port or flag exits 2 before any call; a port that is not shared, or a link Prized refuses (the free credit, the workspace's limit, your role), exits 5 with the reason. JSON: `share PORT` `{"share": {"id", "boxId", "hostname", "port", "slug", "access", "url", "origin", "expiresAt", "createdBy", "createdAt", "openCount", "lastOpenedAt"}, "created", "replaced"}`; `share` `{"enabled", "linksEnabled", "shares": [...]}`; `unshare` `{"share", "revoked": true}`. ### Agents [#agents] | Command | What it does | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `prized agents ls` | One row per coding agent: installed and signed in here and on the box, MCP servers, where it runs. | | `prized agents handoff [AGENT...]` | Copy the named agents' sign-in and MCP config to the box (`--gh`, `--install-cursor`); a Codex ChatGPT sign-in does not travel, so the box signs in through your browser instead and the result shows in your terminal. | | `prized agents run AGENT` | Start an agent detached in a tmux session (`--session`, `--dir`, `--attach`, `-- ARGS`); a paused box wakes first, and `opencode` and `pi` are installed on it if needed (JSON adds `installed`). | | `prized agent upgrade [--version V]` | Upgrade the box agent, which normally updates itself. | See [Agents](https://prized.dev/docs/agents). ### Prompt an agent remotely [#prompt-an-agent-remotely] `prized prompt [BOX] --provider claude|codex|opencode|pi [flags] TEXT` runs the box's coding agent headless on one prompt (`-` reads it from stdin) and follows its events until it answers. Flags: `--model` (`amazon-bedrock/ID`, `openrouter/ID` or `deepseek/ID` picks a backend), `--effort low|medium|high`, `--cwd`, `--continue` (resume the provider's newest session), `--queue` (run after the run in progress), `--detach` (start and return the id), `--auto` (no approvals). OpenCode and pi are installed on the box at their first prompt; a failed install exits 5 with the reason. `prized prompt models [--provider P]` lists known `--model` values with their backend and efforts, without touching a box; JSON `{"models": [{provider, id, backend, efforts, note}], "backends"}`. Each prompt is a turn of a conversation, and conversations run in parallel up to the box's limit: `--new` starts one (the default), `--continue` takes the provider's newest, `--conversation ID` a given one (`--provider` is required here too; box-side metadata never chooses it), and `--queue` waits behind the conversation's turn in progress. `prized prompt conversations [BOX]` lists conversations; `prized prompt ls [BOX] [--conversation ID]` lists runs; `prized events [BOX] [--run ID | --conversation ID] [--follow] [--after N]` shows a run, or follows every run in progress; `prized interrupt [BOX] [--run ID | --conversation ID]` stops every run in progress, or the one named. Only `prompt` wakes a box. Every provider and backend works in conversations alike. Exit: 0 when the run ends cleanly, 1 when it failed (its last verdict an error) or was interrupted, 6 when the conversation has a turn in progress or the box is at its limit, 130 on Ctrl-C. JSON: `prompt --json` prints the started run and does not follow; `prompt --jsonl` and `events --jsonl` print one normalized event per line; `prompt conversations` `{"conversations", "running", "limit"}`; `prompt ls` `{"runs", "active"}`; `events` `{"run", "events", "next", "finished"}`; `interrupt` `{"interrupted", "id", "wasRunning", "signal"}`. Shapes: [Prompt an agent remotely](https://prized.dev/docs/remote-agents). ### Team [#team] `prized team status` is what the team is doing now, `prized team log` what happened; neither needs a box config ([Teams](https://prized.dev/docs/teams)). ```bash prized team status prized team log --since 7d --kind exec,prompt --member ana@acme.com ``` `team status` prints every live box (BOX, OWNER, STATE, IDLE, SESSIONS, SPEND TODAY), every member (MEMBER, ROLE, BOXES, SESSIONS, SPEND (CYCLE), LAST ACTIVE), and a totals line; a contractor gets their own boxes. JSON: the fields of [`GET /api/v1/team/status`](https://prized.dev/docs/api#team). `team log` prints the [audit log](https://prized.dev/docs/teams#audit-log) as TIME, MEMBER, KIND, BOX, DETAIL, ORIGIN (`server` or `reported`); contractors exit 5. Flags: `--since WINDOW` (default `24h`), `--kind KINDS`, `--box BOX`, `--member EMAIL`, `--limit N` (default 100, at most 1000). JSON: `{"events": [{"id", "at", "kind", "origin", "actorKind", "actorUserId", "actorTokenId", "boxId", "detail"}], "nextBefore"}`. ### Health and updates [#health-and-updates] `prized status` is box, sync, ports, and daemon state at a glance; `--watch 2s` keeps it open. JSON: `{"box", "sync", "ports", "daemon", "update": {"available"}}`; `box.transport_path` is `udp`, `relay` (the Prized relay over HTTPS), `ws` (the box's own port 443), `probing` while it looks for a way through, or empty while the tunnel is down. An unreachable box prints `"ok": false` and exits 3. `prized doctor` is the full health check: it applies safe repairs by default, marks them `(fixed)`, and prints only checks that failed, warned, or were fixed ([Troubleshooting](https://prized.dev/docs/troubleshooting)). Flags: `--no-fix`, `--strict` (warnings fail), `-v` (every check), `--bundle` (a diagnostics tarball; nothing leaves your machine); for the agent token and host key, `--rotate-token`, `--fetch-token`, `--forget-hostkey`, `--pin-hostkey K`. JSON: `{"summary": {"pass", "warn", "fail", "skip", "fixed"}, "checks": [{"id", "status", "detail", "hint", "fixed"}]}`. | Command | What it does | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `prized version` (or `prized --version`) | Client, daemon, and agent versions for the box in use (`--box NAME` for another). | | `prized update` | Update prized and the daemon in place from signed releases (`--check` only reports, `--channel` picks one); it also runs by itself ([Updates](#updates)). | | `prized daemon install`, `uninstall`, `start`, `stop`, `restart`, `status`, `logs` | Manage the daemon of the box in use: launchd on macOS, systemd --user on Linux, a Scheduled Task on [Windows](#windows). On Linux with no systemd user session (WSL without systemd, a container), prized runs the daemon itself and nothing restarts it after a reboot: the next `prized setup` or `prized doctor` starts it again. | | `prized daemon proxy` | Show the daemon's proxy (PAC locations are omitted). | | `prized daemon proxy set [URL]`, `prized daemon proxy clear` | Change it. `set` defaults to your shell's `HTTPS_PROXY`, refuses (keeping the old proxy) when there is none it can use, and records a proxy that does not answer from here with a warning. | | `prized mutagen ensure [--from FILE]` | Fetch Prized's managed Mutagen; the installer, `setup`, and `doctor` run it for you. `--from` installs a copy you downloaded yourself, for a network that cannot fetch it. | | `prized ops status OP [--wait]` | Print or poll a deletion operation (`dop_…`) the API returned ([Deletion operations](https://prized.dev/docs/data-retention#deletion-operations)). | `setup`, `login`, and the daemon commands also record your shell's `HTTPS_PROXY` for the daemon, but never replace a recorded proxy with one that does not answer ([Networks that block the tunnel](https://prized.dev/docs/troubleshooting#networks-that-block-the-tunnel)). ### Completions [#completions] ```bash title="zsh" prized completion zsh > "${fpath[1]}/_prized" ``` ```bash title="bash, as root" prized completion bash > /etc/bash_completion.d/prized ``` ```bash title="fish" prized completion fish > ~/.config/fish/completions/prized.fish ``` Tab then completes commands, flags, box names wherever a `BOX` goes, sizes on `resize`, regions on `move`, and windows on `auto-pause`. ## Global flags [#global-flags] | Flag | What it does | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `--json` | Exactly one JSON object on stdout. Interactive commands reject it; never inferred from a pipe. | | `-y, --yes` | Assume yes for confirmations. | | `-q, --quiet` | Suppress non-essential output. | | `-v, --verbose` | Verbose output; repeat for wire detail on stderr. | | `--timeout` | Per-request network timeout, default 10s. | | `--no-color` | Disable ANSI color; `NO_COLOR` is honored too. | | `--box NAME` | The box to act on (`PRIZED_BOX`): the default box, or another connected with `prized setup --box` ([Several boxes](https://prized.dev/docs/boxes#several-boxes)). | | `--config PATH` | The config file to use (`PRIZED_CONFIG`). Overrides `--box`; an escape hatch, not the way to a second box. | ## For agents and scripts [#for-agents-and-scripts] A failing `--json` command prints: ```json { "ok": false, "error": { "code": "CHECK_FAILED", "message": "…", "hint": "…" } } ``` `error.code` maps 1:1 to the exit code, and both are a stable contract: | Exit | `error.code` | Meaning | | ---- | ----------------------- | ---------------------------------------------------------------------------------------------------------- | | 0 | | Success. | | 1 | `INTERNAL`, `AWS_ERROR` | Internal or unexpected error. | | 2 | `USAGE` | A bad flag, an unknown size or region, a window without a unit, `box delete --json` without `-y`. | | 3 | `BOX_UNREACHABLE` | Box unreachable (or Prized itself, from your network), or a `--wait` that ran out its five minutes. | | 4 | `DAEMON_UNAVAILABLE` | Daemon required but unavailable. | | 5 | `CHECK_FAILED` | A precondition failed; the `hint` says what to do. Unknown boxes, sizes not on your plan, refused changes. | | 6 | `CONFLICT` | Already exists or cannot fit: a taken box name, the box count, a balance that cannot run the box, a port. | | 7 | `PARTIAL` | A batch partly failed: some items of a `--all` run or an `agents handoff` succeeded, others did not. | | 8 | `AUTH` | Not signed in, or the token was rejected or expired; run `prized login`. | | 9 | `MISSING_DEP` | A dependency is missing; `prized doctor` usually fixes it. | | 10 | `DECLINED` | You declined a confirmation. | | 130 | | Interrupted. | * **Three exceptions.** `prized ssh` and `prized exec` exit with the remote command's code (ssh's 255 when the box cannot be reached); `prized prompt --jsonl` and `prized events --jsonl` print one object per event; `prized box ls --names` prints a bare JSON array of names. * **`--json` is opt-in.** `prized box ls | head` still prints the table. * **The same token drives the [edge API](https://prized.dev/docs/exec#the-edge-api)** and every route on the [API reference](https://prized.dev/docs/api), with no `prized` installed. * **Sign in without a browser.** `prized login --token dcp_…` takes an API key from the dashboard; in a script use `--token-stdin`, since a command line is visible to other programs. The key keeps its expiry and box limit, and login prints both (`this key expires 2026-12-01, reaches 1 box`); once it expires, every command exits 8 with `this API key has expired`. A key limited to some boxes or to less access cannot register a device key, so [Claude Desktop remote](https://prized.dev/docs/claude-desktop) needs a full-access key. Retire keys under [API keys](https://prized.dev/docs/workspaces#api-keys). * **A whole lifecycle in a script.** `prized box new ci-$RUN --size Small --ttl 2h --wait --json`, work over `prized ssh ci-$RUN -- make test`, then `prized box delete ci-$RUN -y --json --missing-ok` (exit 0 if an earlier run already deleted it); the deadline is the safety net if the script dies. ## Files [#files] | Path | What lives there | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `~/.config/prized/config.toml` | The default box's config: box name, sync projects, port policy, preferred agents (`[agents]`). | | `~/.config/prized/boxes/.toml` | Every other box's config, same schema, with its agent token beside it (`.token`). | | `~/.config/prized/` | API key, SSH identity, per-box certificates, and the proxy recorded from your shell (`proxy.json`, readable only by you). | | `~/.local/state/prized/current-box` | The box `current` names. | | `~/Prized/` | The default local twin of synced projects. | | `~/.ssh/config.d/prized` | The SSH stanza that makes `ssh box-3fa9` work everywhere; prized's own commands read it directly, so they work even when `~/.ssh/config` does not apply its Include line. Each alias is bound to the box it first connected to (`prized box rebind` moves it). | | `~/Library/Logs/prized/` | Daemon logs (`~/.local/state/prized/` on Linux). | On Windows everything but the sync folder and the SSH stanza lives under `%LOCALAPPDATA%\Prized` (`state\`, `cache\`, `logs\`, `mutagen\`, `boxes\`, `bin\`). An install from before the rename keeps its `doppel` names, paths, and `DOPPEL_*` variables; `PRIZED_*` wins when both are set. ## Windows [#windows] The CLI runs on Windows 10 1809 or later and Windows 11, x64 and arm64, from PowerShell or Windows Terminal ([install line](https://prized.dev/docs/quickstart#2-install-the-cli)); `prized ssh` uses the OpenSSH client that ships with Windows. | Topic | What happens | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | The daemon | A Scheduled Task, `PrizedDaemon` (`PrizedDaemon-` for a box connected with `--box`), started at logon, no administrator needed. `daemon stop` and `daemon restart` end it hard, dropping sessions open through it; `prized daemon logs` reads its log. | | No ControlMaster | Windows OpenSSH has no connection sharing, so `prized setup` opens one connection per command, a few seconds slower, and every `prized exec` or `prized ssh -- CMD` opens its own. | | Not on Windows yet | `prized mosh`, `prized mount` and `unmount`, drag-and-drop upload in `prized ssh`, and `prized env` (setup skips the environment step and says so). | | Symbolic links | Not synced: a link inside a synced folder stays on the box; every other file syncs. | | Unsigned binaries | The executables are not Authenticode-signed; see [Troubleshooting](https://prized.dev/docs/troubleshooting#windows). | WSL is Linux to Prized: install with the `curl` line inside the distribution. ## Updates [#updates] At most once every fifteen minutes, after a command finishes, the CLI installs any newer release with the same signed, checksum-verified download as `prized update`, and the next command prints a one-line note. The daemon restarts onto it once no SSH session runs through it (a live `prized ssh` or external SSH client holds it; sync, mounts, port forwards, and mosh do not); `prized version` shows a pending restart, `prized daemon restart` does it now. With [several boxes](https://prized.dev/docs/boxes#several-boxes), each daemon restarts the next time a command reaches its box. Turn it off with `auto = false` under `[update]` in the config, or `PRIZED_NO_AUTO_UPDATE` in your environment. Homebrew installs are left to `brew upgrade prized`, and from-source builds never self-replace. A CLI below the API's minimum exits 5 with a `prized update` hint; releases before 0.7.0 never update themselves, and the dashboard names a machine still on one. ## Usage telemetry [#usage-telemetry] While you are signed in, the CLI records which command ran, its version, OS, duration, and exit code, and sends the counts in occasional batches; it never records arguments, flags, paths, or file contents, and sends nothing while you are signed out. A failed `prized setup` step, or a `prized doctor` report with a failing check, adds one word from a fixed list naming what failed (for doctor, the first failing check), never the message you saw. CLI use also becomes at most one product event per workspace per UTC day, with the first command's name, the CLI version, and OS; updates and automatic connections do not count ([Privacy](https://prized.dev/docs/privacy#website-and-dashboard)). Turn it off either way: ```toml # in ~/.config/prized/config.toml [telemetry] enabled = false ``` ```bash # or per shell export PRIZED_NO_TELEMETRY=1 ``` This covers the CLI only; for the website and dashboard, see [Privacy](https://prized.dev/docs/privacy). --- # API reference > Every HTTP route an API key can call: the control plane for boxes and workspace settings, the edge for commands, files, agent prompts, and stopping the desktop. URL: https://prized.dev/docs/api ## Two hosts, one key [#two-hosts-one-key] | What | Control plane | Edge | | -------- | --------------------------------------------- | --------------------------------------------- | | Base URL | `https://api.prized.dev/api/v1` | `$EDGE/v1/box/{box}/` | | A box is | its `id` (a uuid) | its name or id | | Errors | `{"error": "code"}`, sometimes with `message` | `{"ok": false, "error": {"code", "message"}}` | Both take the same bearer. Read the edge's address from `edge.url` in `GET /api/v1/me` rather than assuming one. Sandboxes have their own routes on both hosts, with the same key: [Sandbox API](https://prized.dev/docs/sandboxes-api). ```bash export PRIZED_API_KEY=dcp_… API=https://api.prized.dev/api/v1 AUTH="Authorization: Bearer $PRIZED_API_KEY" EDGE=$(curl -s "$API/me" -H "$AUTH" | jq -r .edge.url) ``` ## Authentication [#authentication] Send `Authorization: Bearer dcp_…` on every request, with an API key: make one per script or agent with **New key** under **Dashboard → Settings → API keys** (`prized login --token dcp_…` signs a machine in with one), or, for someone new to Prized, let a coding agent make the account and get its own key with an emailed code ([Agent sign-in](https://prized.dev/docs/agent-sign-in)). An agent key whose code has not come back yet gets `403 verification_required` everywhere but `GET /api/v1/me`. A revoked key gets `401` on its next request. A key made with limited access (read only, or per area) gets `403 key_access_denied` on anything outside it, with `area`, `level` and a `message` naming what it lacks ([Workspaces: What a key can do](https://prized.dev/docs/workspaces#what-a-key-can-do)). * **What a key can do.** It acts as you on every box, snapshot, environment and secret in its workspace; a [contractor](https://prized.dev/docs/teams#roles)'s key reaches only their own boxes, and no secrets. Minting and rotating credentials, membership and roles, policies, the audit export, billing, and closing the workspace are dashboard-only and answer `401 unauthorized` to any bearer, so a leaked key cannot widen itself. * **Expiry.** A key past its expiry gets `401 token_expired`, with `expiresAt`, on every route and on the edge. `GET /api/v1/me` says when the calling key expires ([When a key expires](https://prized.dev/docs/workspaces#when-a-key-expires)). * **A key for some boxes.** A key limited to boxes answers `404 not_found` for any other box, as if it did not exist, and leaves other boxes out of every list. Workspace-wide routes (creating boxes and forks, secrets, environments, sandboxes, team activity, the audit log, workspace settings) answer `403 key_access_denied` with `scope: "boxes"` ([Limit a key to some boxes](https://prized.dev/docs/workspaces#limit-a-key-to-some-boxes)). * **Other credentials.** A dashboard terminal ticket works for its one box only, on the box read, wake and connect routes and the edge's exec, files and prompt routes; monitor tickets, phone passwords and shared-port viewer cookies work nowhere. * **The version header.** Scripts should not send `X-Prized-CLI-Version`, the CLI's header: below the minimum it answers `426 cli_update_required` with `minVersion`. ## Conventions [#conventions] | Topic | Rule | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | JSON in, JSON out | Send `Content-Type: application/json`. Bodies cap at 64 KiB (128 KiB on the secrets routes; 2 MiB on environment create, patch, files and repos); over the cap or not JSON is `400 bad_request`. A `204` has no body; with `Accept-Encoding: gzip`, answers over 1 KiB are gzipped. | | Ids | `GET /api/v1/boxes` maps box names to ids. Snapshots are `snp_…`, environments `env_…`, secrets `sec_…`, grants `sgr_…`, operations `dop_…`. An unknown, malformed, or foreign id is `404 not_found`, with nothing written. | | 202 means in progress | Creating, waking, pausing, resizing, moving, forking, restoring and deleting a box answer `202` with the row as written: `desiredState` has moved; `observedState` follows within seconds to minutes. Poll `GET /api/v1/boxes/{id}`, or `GET /api/v1/operations/{id}` for a delete. Setting writes answer `200` or `201`. | | Errors | Codes are stable; messages are for people. Refusals carry their numbers: `insufficient_balance` has `balanceMicros`, `requiredMicros`, `burnMicrosPerHour`; `snapshot_limit` has `limit`; `box_not_running` has `state`. | | Idempotency | Creating, forking, resizing and moving a box, and creating or restoring a snapshot, take an `Idempotency-Key` header that makes a retry safe ([Idempotency keys](#idempotency-keys)). Deletes are idempotent; wake or suspend on a box already in that state is a `202` no-op. | | Limits | The control plane's are counts: `409 box_limit`, `429 snapshot_limit`, `402 secret_limit`, `429 customer_session_limit` (CLI tunnel sessions). The edge admits 8 concurrent requests and 20 per second, per token and per box, then answers `429 rate_limited` with `Retry-After` ([Limits](https://prized.dev/docs/limits#commands-and-files)). | | Times and money | Timestamps are RFC 3339 UTC. Balances are micro-dollars (`1000000` is $1). | | Stability | The CLI and the dashboard use this API; the box object is what `prized … --json` prints. Fields and routes are added under `/v1` without notice, so ignore unknown fields. CLI-internal routes are not listed. | An empty balance's `wake_rejected` adds `reason`, `cause` and `billingUrl`. ## Idempotency keys [#idempotency-keys] A create whose answer is lost (a timeout, a dropped connection, a `5xx`) may or may not have made the box. Send an `Idempotency-Key` header with a value of your own, unique per request (a UUID), and a retry of that request within 24 hours gets the first answer back instead of a second box: ```bash KEY=$(uuidgen) curl -s --retry 3 --retry-all-errors -X POST "$API/boxes" -H "$AUTH" -H "Idempotency-Key: $KEY" -H "Content-Type: application/json" -d '{"tier":"flow"}' ``` The header works on `POST /api/v1/boxes`, `POST /api/v1/boxes/{id}/fork`, `POST /api/v1/boxes/{id}/resize`, `POST /api/v1/boxes/{id}/region`, `POST /api/v1/snapshots` and `POST /api/v1/snapshots/{id}/restore`. | When | Answer | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | A retry with the same key and the same request | The first answer, status and body, with `Idempotent-Replayed: true`. Nothing is created, billed or counted against a limit again; the body is the box as it was then, so read `GET /api/v1/boxes/{id}` for its state now | | The first request with the key is still running | `409 idempotency_in_progress` with `Retry-After`; retry with the same key | | The same key on a different request: another body, another id in the path, another member, or the same member with another role or API key box scope | `422 idempotency_key_reused` | | A key that is not 1 to 128 printable ASCII characters without spaces | `400 invalid_idempotency_key` | | The first request was refused (any `4xx`) | Nothing is kept: fix the cause and retry with the same key | | More than 24 hours after the first answer | A new request | A changed box scope refuses the replay rather than returning metadata from the earlier scope. Two credentials for the same member and the same box set can replay each other’s answer; the order of the box ids does not matter. A key belongs to its workspace and route: the same value on another route, or in another workspace, is another key. Without the header, a retried create makes a second box, unless it names one (then `409 name_taken`). ## Account [#account] | Route | Answer | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /api/v1/me` | `200 {customer, user, workspace, billing, boxes, edge, key}`: who the key acts as, the workspace, where the edge is, and the calling key. Open to any live key | ```json { "customer": { "id": "3f9c…", "email": "you@example.com", "githubLogin": "you", "status": "active", "onboardingStep": "done", "onboardingCompletedAt": "2026-08-29T12:04:00Z", "createdAt": "2026-08-29T11:58:00Z", "name": "Acme" }, "user": {"email": "you@example.com", "name": "Your Name", "role": "owner"}, "workspace": { "id": "3f9c…", "name": "Acme", "zeroDataRetention": false, "closingAt": null, "purgeAfter": null }, "billing": { "balanceMicros": 41250000, "burnMicrosPerHour": 160000, "runwayHours": 257.8125, "warnBelowHours": 48, "planAmountUsd": 50, "overage": true, "collecting": false, "paymentFailed": false, "paymentGraceUntil": null }, "boxes": [ {"id": "5d0a…", "hostname": "mango", "observedState": "running"} ], "edge": {"url": "https://…"} } ``` * **`customer`** is the workspace: `customer.id` is the workspace id. * **`user`** is who the key acts as, with their `role`; `null` for a key with no user. * **`billing`** says why an empty balance may not stop boxes: `overage`, `collecting` (a renewal collecting, or a failed one in its grace), `paymentFailed`, `paymentGraceUntil`. `runwayHours` is `null` while nothing is burning. Also `trial` (free credit) and `billingUrl`. * **`boxes`** is every box, terminated included (a contractor's own), each the [box object](#boxes), shortened above. `box` (the oldest live box in `boxes`) is deprecated; read `boxes`. * **`key`** is the calling API key: `{id, kind, expiresAt, boxIds}`, where `kind` is `machine` (a `prized login`, whose `expiresAt` moves with use) or `api` (**New key**), `expiresAt` is `null` for a key that never expires, and `boxIds` is `null` unless the key is limited to boxes, whose ids it lists (then `boxes` holds only those). Absent for a dashboard session. * **A limited key** adds `access`, its level per area. Without Boxes: Read it gets `boxes: []`; without Workspace: Read it gets `billing: null`, a `workspace` of only `id` and `name`, and `null` for `customer.email` and `customer.githubLogin`. ## Boxes [#boxes] | Route | Body | Answer | | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `GET /api/v1/boxes` | query `live=1` leaves out boxes whose `observedState` is `terminated` | `200 {boxes}`, newest first | | `POST /api/v1/boxes` | `{name?, tier?, region?, ttlMinutes?, autoPauseMin?, environment?, env?, restricted?, fromSnapshotId?, storage?}` | `202 {box}` with `observedState: "requested"`; poll until `running` | | `GET /api/v1/boxes/{id}` | | `200 {box, state, vitals, events, job, pausesSurvived, environment, envVars, restricted}` | | `PATCH /api/v1/boxes/{id}` | `{autoPauseMin?, ttlMinutes? \| pauseAt?, autoSnapshotHours?, autoSnapshotKeep?, quietHoursExempt?}` or, alone, `{env}` | `200 {box}` | | `DELETE /api/v1/boxes/{id}` | | `202 {box, operation}`; see [Operations](#operations) | | `POST /api/v1/boxes/{id}/wake` | | `202 {box}`, `desiredState: "running"` | | `POST /api/v1/boxes/{id}/suspend` | | `202 {box}`, `desiredState: "suspended"` | | `POST /api/v1/boxes/{id}/resize` | `{target_tier}` | `202 {box}` with the new `tier`; the box restarts | | `POST /api/v1/boxes/{id}/region` | `{region}`: `us-west-2`, `us-west-1`, `us-east-1`, `us-east-2` or `eu-central-1` | `202 {box}` with `desiredRegion` set; `region` changes when the move lands | | `POST /api/v1/boxes/{id}/fork` | `{name?, tier?, keepSnapshot?}` | `202 {box, snapshot}` | | `GET /api/v1/boxes/{id}/metrics` | query `from`, `to` (epoch seconds or `…Z` ISO 8601; default the last hour, clamped to 30 days) | `200 {v, res, from, to, buckets, series, fs, ifaces}`; `res` is 30, 300 or 3600 seconds by span | | `GET /api/v1/boxes/{id}/procs-at` | query `t` (required) | `200 {v, at, procs, ports}`: the process and port snapshot nearest `t`, within five minutes, else `404` | A box-scoped ticket or phone password lists only its bound box. Once either `desiredState` or `observedState` is `terminated`, that list is empty, with or without `live=1`. Session and CLI-token lists keep their normal history. A create refused with `insufficient_balance` includes `message`, `balanceMicros`, `requiredMicros`, and `admissionMicrosPerHour`. The allowance includes unbilled failed-launch cleanup reservations; it is not a charge. `burnMicrosPerHour` remains a compatibility alias for that allowance in this error response only. Display `message` when available. **The box object** is the one on [CLI: The box object](https://prized.dev/docs/cli#the-box-object), plus `autoSnapshotHours`, `autoSnapshotKeep`, `environmentId`, `environmentVersionNo`, `envVars` (`[{name, value}]`), `restricted`, `ownerUserId` (the member who created it), `quietHoursExempt`, `storageBackend` (`ebs` for a box on its own disk, `ext4_s3` for [infinite storage](https://prized.dev/docs/boxes#infinite-storage)) and `storageUsedBytes` (the space an infinite storage box last reported in use; `null` otherwise or until it reports). `desiredState` is `running`, `suspended` or `terminated`; `observedState` is one of `requested`, `provisioning`, `bootstrapping`, `running`, `suspending`, `suspended`, `waking`, `deep_sleep`, `resizing`, `migrating`, `degraded`, `provision_failed`, `terminating`, `terminated` ([Boxes: States](https://prized.dev/docs/boxes#states)). **Creating:** | Field | Rule | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | A hostname: lowercase letters, digits and hyphens, starting and ending with a letter or digit, 1 to 32 characters; a fruit if absent | | `tier` | A size id: `nano`, `micro`, `lite`, `flow`, `pro`, `max`, `ultra`, or a [GPU size](https://prized.dev/docs/gpu-boxes) (`gpu-l4`, `gpu-l40s`). If absent, the control plane picks what the balance runs comfortably (a Small on the [free credit](https://prized.dev/docs/billing#free-credit) alone). The free credit starts only `nano` through `flow`; others answer `409 tier_unavailable`, with `allowed` naming the sizes it does | | `region` | Where a fresh box starts: `us-west-2` (the default), `us-west-1`, `us-east-1`, `us-east-2` or `eu-central-1` ([Regions](https://prized.dev/docs/machines#regions)); anything else, or a region beside `fromSnapshotId`, is `400 invalid_region` | | `ttlMinutes` | 5 to 43200: a [pause deadline](https://prized.dev/docs/boxes#pause-deadline) | | `autoPauseMin` | 30 to 10080, or `null`: the [idle window](https://prized.dev/docs/boxes#auto-pause). Off when absent; 120 on the [free credit](https://prized.dev/docs/billing#free-credit) | | `environment` | An id or name; `null` for none, absent for your default | | `env` | `{NAME: "value"}` | | `restricted` | Makes the box [safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties); a contractor's box always is | | `fromSnapshotId` | A snapshot id: restore instead of booting fresh, in the snapshot's region (a `region` beside it is `400 invalid_region`); `tier` may be any size whose disk holds the snapshot | | `storage` | `"infinite"` for [infinite storage](https://prized.dev/docs/boxes#infinite-storage) (Extra Small and up, a fresh box in a region that offers it, currently `us-west-2`; otherwise `422 storage_unavailable`, `message` says why; a region refusal names the regions that offer it), `"standard"` for the box's own disk, the default (an alpha workspace's default is infinite where the box can have it); anything else is `400 invalid_storage` | **The detail answer.** `events` is the last 50 state changes as `{id, fromState, toState, reason, actor, at}`; `job` is the lifecycle change in flight (`op`, `step`, `attempt`, `lastError`, `nextRetryAt`) or `null`; `vitals` is the last heartbeat (`cpu_pct`, `mem_pct`, `disk_pct`, `idle_for_sec`, `listeners_count`, `agent_version`); `environment` is `{id, name, versionNo, latestVersionNo}` or `null`. **Patching** takes one family per request: the pause settings and snapshot schedule together (`autoPauseMin`; `ttlMinutes` or `pauseAt`, not both, `null` to clear; `autoSnapshotHours` 1 to 168 or `null`; `autoSnapshotKeep` 1 to 10), or `{env}` alone, replacing the per-box vars (100 names, 64 KiB in all). A deadline needs a running box (`409 box_not_running`); clearing one always works. `autoPauseMin` follows the create rule on the free credit, and a member's above the workspace's [auto-pause floor](https://prized.dev/docs/teams#policies) is `403 policy_denied`; only the owner may set `quietHoursExempt`, which keeps the box running through [quiet hours](https://prized.dev/docs/teams#quiet-hours) (`403 not_owner`). | HTTP | `error` | When | | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `bad_request`, `invalid_name`, `invalid_tier`, `invalid_region`, `invalid_storage`, `invalid_env`, `invalid_auto_pause`, `invalid_pause_at`, `invalid_auto_snapshot`, `environment_too_large` | The body is off; `message` names the window or the rule | | 400 | `resize_rejected`, `move_rejected` | A box mid-transition, a second move of the box or a fourth in the workspace within 24 hours, or a region the box cannot use | | 403 | `restore_tier_not_allowed` | The size is off your plan, or its disk cannot hold the snapshot | | 403 | `gpu_unavailable` | A [GPU size](https://prized.dev/docs/gpu-boxes) this workspace cannot start: `reason` is `not_offered` (not in the beta), `region_unavailable` (GPU boxes run in `us-west-2` only during the beta), `plan_required` (no paid plan invoice) or `gpu_box_limit` (its GPU box is in use); `message` says which. Create, fork, restore, resize, wake and a region move answer it | | 403 | `policy_denied`, `not_owner` | A [workspace policy](https://prized.dev/docs/teams#policies) refused a member (`rule` is `memberSpendCapUsd`, `memberBoxLimit`, `memberMaxTier`, `autoPauseFloorMin` or `contractor_restricted`); a quiet-hours exemption from anyone but the owner | | 404 | `not_found` | No such box, snapshot or environment in this workspace, or a box a contractor does not own | | 409 | `box_limit`, `name_taken`, `insufficient_balance`, `tier_unavailable`, `workspace_closing`, `workspace_held`, `wake_rejected`, `box_failed`, `snapshot_in_progress`, `box_not_running` | A conflict with the account or the box, by code below | | 422 | `storage_unavailable`, `storage_backend_unsupported` | Infinite storage this create cannot have (`message` names the workspace, size, region, or snapshot reason); a snapshot (by hand or a schedule) or fork of an [infinite storage](https://prized.dev/docs/boxes#infinite-storage) box | **The 409s, by code:** * `box_limit`: The plan's box count (1 on the [free credit](https://prized.dev/docs/billing#free-credit) alone; with `message` and `limit`). * `name_taken`: A live box with that name. * `insufficient_balance`: A balance short of four hours. * `tier_unavailable`: A size the plan or the free credit refuses (`message` says which; `allowed` lists the free credit's sizes). * `workspace_closing`: A closing workspace. * `workspace_held`: A workspace paused for [mining](https://prized.dev/docs/limits#not-allowed) or for [filling the box's CPU on the free credit](https://prized.dev/docs/billing#free-credit) (`message` says which and what lifts it; resize and move answer it too). * `wake_rejected`: A wake the balance refuses. * `box_failed`: A wake of a box that failed to launch (`provision_failed`; delete it). * `snapshot_in_progress`: A fork while another snapshot of the box is being taken. * `box_not_running`: A deadline on a paused box. ## Command history [#command-history] What `prized exec` and the edge's `POST /exec` ran on a box, read from the control plane, so it answers while the box is paused ([Run commands and files: Command history](https://prized.dev/docs/exec#command-history)). A box's records are visible to whoever can see the box. | Route | Query | Answer | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `GET /api/v1/boxes/{id}/commands` | `limit` (1 to 200, default 50), `before` (the previous page's `nextBefore`), `mode` (`sync` or `detached`), `status` | `200 {commands, nextBefore}`, newest first; `nextBefore` is `null` on the last page | | `GET /api/v1/boxes/{id}/commands/{commandId}` | | `200 {command}` | | `GET /api/v1/boxes/{id}/commands/{commandId}/output` | `stream` (`stdout`, the default, or `stderr`), `offset` (bytes, default 0), `limit` (1 to 1048576, the default), `encoding` (`utf8`, the default, or `base64`) | `200 {stream, encoding, offset, nextOffset, size, skipped, data, status, done}` | | `DELETE /api/v1/boxes/{id}/commands/{commandId}` | | `204`; writes a `command_history.delete` audit row | | `DELETE /api/v1/boxes/{id}/commands` | | `200 {deleted}`: every record of the box | ```json { "id": "p_3f9c0a1b2d4e", "boxId": "5d0a…", "mode": "detached", "source": "cli", "command": "./train.sh", "commandTruncated": false, "cwd": "/home/you/app", "status": "exited", "exitCode": 0, "signal": null, "pid": 41213, "startedAt": "2026-10-11T14:02:11Z", "finishedAt": "2026-10-11T14:05:40Z", "actor": {"kind": "cli", "userId": "…", "tokenId": "…"}, "stdoutBytes": 1830, "stderrBytes": 0, "stdoutTruncated": false, "stderrTruncated": false, "outputKept": true } ``` * **`status`** is `running`, `exited`, `killed`, `timed_out`, `failed`, or `lost`; `exitCode`, `signal`, `pid`, and `finishedAt` are `null` until known. `source` is `cli` (`prized exec`) or `api` (`POST /exec`). * **`command`** is the first 8,192 characters (`commandTruncated` says when it was cut). Under [zero data retention](https://prized.dev/docs/data-retention#zero-data-retention) `command` and `cwd` are `null` and `outputKept` is `false`. * **Output pages** are bytes of one stream from `offset`. Read again from `nextOffset` until `done` (the command ended and every byte has been read). `size` is the stream's length; `skipped` is how many bytes from your `offset` are no longer kept (up to the last 1 MiB of a stream is; a 256-chunk cap may trim heavily fragmented output sooner), and the page then starts later. `utf8` never ends a page inside a character; for exact bytes ask for `base64`. * **A limited key** needs Boxes: Write for every history route, reads included: command lines and output can carry secrets. A key limited to some boxes reaches only those boxes' history; any other box is `404 not_found`. ## Shared ports [#shared-ports] A shared port is a link to one port on a box, for one audience: `members` (signed-in workspace members who can see the box, under `p.prized.dev`), `anyone` (whoever holds the link, which carries a secret), or `public` (anyone at all) ([Ports: Share a port](https://prized.dev/docs/ports#share-a-port)). | Route | Body | Answer | | ------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `POST /api/v1/boxes/{id}/port-shares` | `{port, access?, expiresIn?}` | `201 {share}` (with `replaced: {id, access}` when it replaced the port's link of another audience); `200 {share}` when that port already has a link of that audience, or has any link and the body names no `access`, unchanged | | `GET /api/v1/boxes/{id}/port-shares` | | `200 {enabled, linksEnabled, modes, shares, listening}`: the box's live links, oldest first, and `listening` `{at, ports: [{port, process, pid}]}`, the ports it last reported listening on (`null` when it has not reported in the last few minutes) | | `GET /api/v1/port-shares` | | `200 {enabled, linksEnabled, modes, shares}`: every live link on a box you can see | | `DELETE /api/v1/port-shares/{id}` | | `200 {revoked: true}`; the link stops working for everyone on it | * **The body**: `port` 1 to 65535; `access` `members` (the default), `anyone` or `public`; `expiresIn` in seconds for `anyone` and `public` only, 300 to 2592000, 604800 (7 days) by default. Anything else is `400 bad_request`. * **The share object**: `id`, `boxId`, `hostname`, `port`, `slug`, `access`, `url`, `origin`, `expiresAt` (`null` for members), `createdBy` (the member who shared it), `createdAt`, `openCount`, `lastOpenedAt`. `url` is the link to hand out; an anyone link's carries its secret in the `201` that made it and is `null` everywhere after. * **`modes`**: for each audience, `{"allowed": true}` or `{"allowed": false, "error", "message"}` with what a create would answer, so a client can grey out what you cannot pick. * **Off**: `enabled: false` means members links are off here (a create answers `503 port_share_disabled`); `linksEnabled: false` means anyone and public links are (`503 share_links_disabled`). | HTTP | `error` | When | | ---- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 402 | `share_links_need_payment` | An anyone or public link on the [free credit](https://prized.dev/docs/billing#free-credit) | | 403 | `not_allowed`, `policy_denied`, `share_links_blocked` | A contractor's link outside the workspace, a public link from anyone but the owner, or a key with no member behind it; the `anyoneLinksOwnerOnly` policy (`rule`); links turned off for the workspace | | 404 | `not_found` | No such box, a box a contractor does not own, or a link already stopped | | 409 | `share_link_limit` | The workspace already has 10 live anyone and public links (`limit`) | ## Cloud federation [#cloud-federation] The IAM roles the workspace's boxes assume ([AWS access](https://prized.dev/docs/aws)). Owners and members read, only owners write (`403 not_owner`), and a contractor gets `403 not_allowed`. If federation is off on this deployment, the list answers `enabled: false`, and connect and verify answer `503 aws_federation_disabled`. | Route | Body | Answer | | ------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /api/v1/aws-roles` | | `200 {enabled, issuer, audience, workspaceId, subjectPattern, tokenPath, limit, roles}` | | `POST /api/v1/aws-roles` | `{roleArn, profile?}` | `201 {role}`, verified against AWS at once (`verifiedAt`, `verifiedArn` or `verifyError`); `200 {role}` when that ARN is already connected; `400 invalid_role_arn` / `invalid_profile`; `409 already_connected` (a different profile for a connected ARN: remove, then connect) / `profile_taken` / `aws_roles_limit` / `workspace_closing` | | `POST /api/v1/aws-roles/{id}/verify` | | `200 {role}` with fresh verification fields | | `DELETE /api/v1/aws-roles/{id}` | | `200 {revoked: true}`; every box drops the profile on its next refresh | | `POST /api/v1/aws-connect` | `{profile?}` | `201 {id, url, expiresAt}`: a single-use, one-hour link that opens a pre-filled CloudFormation stack in the AWS console; the stack reports the role back, and it connects only after AWS confirms the workspace can assume it. A new link cancels the last. `503 aws_quick_connect_unavailable` when this deployment has no published template | | `DELETE /api/v1/aws-connect/{id}` | | `200 {cancelled: true}`; `404` once spent, expired or cancelled | ## Operations [#operations] | Route | Answer | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `GET /api/v1/operations/{id}` | `200 {operation}`: `id`, `kind` (`box_delete`, `snapshot_delete`), `targetId`, `status`, `requestedAt`, `completedAt`, `error` | Deletes answer with one. `status` runs `pending`, `processing`, then `completed`, or `failed` with `error`; finished operations stay readable for 30 days. What a delete keeps and removes is on [Data retention](https://prized.dev/docs/data-retention#deletion-operations). ## Snapshots [#snapshots] | Route | Body | Answer | | ------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------- | | `GET /api/v1/snapshots` | query `transient=1` to include fork copies | `200 {snapshots}`, newest first | | `POST /api/v1/snapshots` | `{boxId, name, description?}` | `201 {snapshot}` with `status: "creating"`; poll the list until `available` | | `DELETE /api/v1/snapshots/{id}` | | `202 {snapshot, operation}` | | `POST /api/v1/snapshots/{id}/restore` | `{name?, tier?}` | `202 {box}`, a new box from the snapshot | A snapshot is `{id, name, description, status, kind, expiresAt, boxId, sourceBoxHostname, tier, region, sizeGb, error, createdAt}` (`status`: `creating`, `available`, `failed`, `deleting`; `kind`: `manual`, `fork`, `auto`, `final`); other fields may disappear. Names are 1 to 64 characters of letters, digits, `.`, `_` and `-`, starting and ending with a letter or digit. Forking a box is under [Boxes](#boxes); what each kind means is on [Snapshots](https://prized.dev/docs/snapshots). Refusals: `409 snapshot_in_progress`, `409 no_volume`, `409 insufficient_balance`, `409 name_taken`, `409 box_limit`, `409 workspace_held`, `422 storage_backend_unsupported` (a box with [infinite storage](https://prized.dev/docs/boxes#infinite-storage)), `429 snapshot_limit`, `403 restore_tier_not_allowed`. Templates were removed: `POST` and `DELETE /api/v1/snapshots/{id}/template`, `POST /api/v1/boxes/{id}/template`, and `fromTemplate` on `POST /api/v1/boxes` answer `410 templates_removed`; restore a snapshot by id instead. ## Environments [#environments] | Route | What it does | | -------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `GET /api/v1/environments` | `200 {environments}` | | `POST /api/v1/environments` | Create; `201 {environment}` | | `PATCH /api/v1/environments/{id}` | Change any field; a config change mints a version; `200 {environment}` | | `DELETE /api/v1/environments/{id}` | `204`; boxes keep their version | | `POST /api/v1/environments/{id}/default` | Make it the default for new boxes | | `POST /api/v1/environments/{id}/toggles` | The safe-for-third-parties switch and the three credential channels | | `POST /api/v1/environments/{id}/vars`, `DELETE …/vars/{name}` | Set or remove one env var | | `PUT /api/v1/environments/{id}/files`, `DELETE …/files?path=` | Set or remove one secret file | | `POST /api/v1/environments/{id}/repos`, `DELETE …/repos?repo=` | Add or remove one repo | | `POST /api/v1/environments/{id}/upgrade` | Pin live boxes to the latest version; `200 {upgraded, skipped, latestVersionNo}` | Bodies, the environment object, limits and error codes are on [Environments: API](https://prized.dev/docs/environments#api). ## Secrets [#secrets] Brokered credentials: the box sees a placeholder, swapped for the value only in HTTPS requests to the secret's `hosts` ([Credentials](https://prized.dev/docs/credentials#secrets-for-agents), [Connectors](https://prized.dev/docs/connectors)). | Route | Body | Answer | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `GET /api/v1/secrets` | query `box={id}` for only the ones that box may mount | `200 {secrets}`, never a value | | `POST /api/v1/secrets` | `{name, value, description?, hosts?, grants?}`; `grants`, a list of `{boxId}`, `{userId}` or `{email}`, is the scope the secret is born with, committed with it | `201 {secret}`; `404` (a grant's box), `404 member_not_found` | | `PATCH /api/v1/secrets/{id}` | `{value?, description?, hosts?}` (at least one) | `200 {secret}`; a new value keeps the placeholder and stamps `lastRotatedAt` | | `DELETE /api/v1/secrets/{id}` | | `204`; its mounts, grants and usage go with it | | `POST /api/v1/secrets/{id}/grants` | `{boxId}`, `{userId}` or `{email}` (one of the three) | `201 {grant}`; an existing grant answers `201` again | | `DELETE /api/v1/secrets/{id}/grants/{grantId}` | | `204` | | `GET /api/v1/secrets/{id}/usage` | | `200 {uses}`: `[{boxId, hostname, host, firstAt, lastAt, count}]`, up to 1,000 rows | | `GET /api/v1/boxes/{id}/secrets` | | `200 {mounts}` | | `POST /api/v1/boxes/{id}/secrets` | `{secretId, envName}` to mount an existing secret, or `{name, value, envName, hosts?, description?}` to create and mount in one step | `201 {mount}` | | `DELETE /api/v1/boxes/{id}/secrets/{mountId}` | | `204` | A secret is `{id, name, description, hosts, placeholder, mountCount, scope, grants, createdAt, updatedAt, lastRotatedAt}`, where `scope` is `workspace` (no grants) or `granted` ([Teams: Scoped secrets](https://prized.dev/docs/teams#scoped-secrets)) and `grants` is `[{id, boxId, hostname, userId, email, createdAt}]`; a mount is `{id, envName, secret: {id, name, placeholder, hosts}}`. `name` is `UPPER_SNAKE_CASE` (up to 128 characters), `value` up to 16 KiB, `hosts` up to 32 entries, each a hostname or a `*.` wildcard; `envName` is a shell identifier. | HTTP | `error` | When | | ---- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `bad_request`, `invalid_name`, `invalid_host`, `invalid_env_name` | The body is off | | 402 | `secret_limit` | The workspace's secret count is full | | 403 | `secret_not_granted`, `not_allowed` | A mount of a secret the box may not have (the create-and-mount form too; nothing is committed); a contractor on any secrets route or box mount | | 404 | `not_found`, `member_not_found` | No such secret, grant or box (a terminated box included); a grant to a user id or email that is not a current member | | 409 | `name_taken`, `env_name_taken`, `box_restricted` | A secret with that name, a mount on that variable, a box that is [safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties) | ## Policies [#policies] | Route | Answer | | -------------------------------- | ------------------------------------------------------------------------------------------- | | `GET /api/v1/workspace/policies` | `200 {policy, updatedBy, updatedAt}`; owners and members; a contractor is `403 not_allowed` | `policy` is `{memberSpendCapUsd, memberBoxLimit, memberMaxTier, autoPauseFloorMin, quietHours, auditRetentionDays}`, each `null` while off except `auditRetentionDays` (90 by default); `quietHours` is `{start, end, days, allDayDays, timezone}`. `PATCH /api/v1/workspace/policies` (dashboard session only) takes the keys to change, `null` clearing one: `400 invalid_policy` with `field` and `message`, `403 not_owner`, `409 workspace_closing`. A create, wake, resize or fork that a policy refuses answers `403 policy_denied` with `rule` and `message`; bounds and rules are on [Teams: Policies](https://prized.dev/docs/teams#policies). ## Audit log [#audit-log] | Route | Body or query | Answer | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /api/v1/audit` | query `since`, `until` (RFC 3339 UTC or epoch seconds), `kind` (comma list), `box` (id), `actor` (user id), `origin` (`server` or `client`), `before` (a row id cursor), `limit` (1 to 1000, default 100) | `200 {events, nextBefore}`, newest first; `nextBefore` is `null` on the last page | | `GET /api/v1/audit?format=csv` | the same filters | `text/csv` as an attachment `audit-YYYYMMDD-HHMMSS.csv`, columns `id,at,kind,origin,actor_kind,actor_user_id,actor_token_id,box_id,detail`, every row of the range up to 50,000 | | `POST /api/v1/audit` | `{kind, boxId, detail?}` | `201 {id}`: a client report, from an API key or a terminal ticket | An event is `{id, at, kind, origin, actorKind, actorUserId, actorTokenId, boxId, detail}`; `actorKind` is `user`, `cli`, `ticket`, `mobile` or `system`. Owners and members read; a contractor is `403 not_allowed`. Clients may report only `exec`, `file.upload`, `file.download`, `prompt` and `desktop.start` (`detail`: 16 keys of up to 40 characters, strings of up to 512; body: 64 KiB), and a ticket only on its own box ([Teams: Audit log](https://prized.dev/docs/teams#audit-log)). Reports for a box being deleted or already deleted return `404 not_found`; its existing audit history remains readable. `box.connect` records SSH certificate issuance, not a successful connection. The dashboard labels it **SSH certificate issued**; the API, CSV and scheduled export keep the `box.connect` kind. A failed connection can still have this row ([Teams: Audit log](https://prized.dev/docs/teams#audit-log)). | HTTP | `error` | When | | ---- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | 400 | `invalid_time`, `invalid_kind`, `invalid_box`, `invalid_actor`, `invalid_origin`, `invalid_cursor`, `invalid_limit`, `invalid_format` | The named query parameter is off; `message` says the shape | | 400 | `invalid_kind`, `bad_request` | A report of a kind clients may not claim, or a body that is off | | 403 | `not_allowed` | A contractor reading, or a monitor ticket reporting | | 404 | `not_found` | A report naming another workspace's box, or a ticket naming a box other than its own | ## Team [#team] | Route | Answer | | ------------------------- | ----------------------------------------------------------------------- | | `GET /api/v1/team/status` | `200 {now, cycle: {start, end}, scope, boxes, members, totals, recent}` | **API key access:** Workspace: Read (or Write). Boxes-only keys receive `403 key_access_denied` with `area: "workspace"` and `level: "read"`; the response includes workspace members, spend and recent activity, not just boxes. Full-access keys and browser sessions keep their existing role-based view. ```json { "now": "2026-09-10T17:20:00Z", "cycle": {"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"}, "scope": "workspace", "boxes": [ { "id": "5d0a…", "hostname": "mango", "tier": "flow", "desiredState": "running", "observedState": "running", "ownerUserId": "7Hq2…", "ownerName": "Kat", "ownerEmail": "kat@example.com", "idleForSec": 0, "cpuPct": 12, "memPct": 41, "lastHeartbeatAt": "2026-09-10T17:19:42Z", "sessions": 1, "lastPromptAt": "2026-09-10T16:58:03Z", "lastExecAt": null, "spendTodayMicros": 1920000, "spendCycleMicros": 28800000 } ], "members": [ { "userId": "7Hq2…", "name": "Kat", "email": "kat@example.com", "role": "member", "boxes": 1, "sessions": 1, "spendCycleMicros": 28800000, "lastActiveAt": "2026-09-10T16:58:03Z" } ], "totals": { "spendTodayMicros": 1920000, "spendCycleMicros": 28800000, "running": 1, "sessions": 1 }, "recent": [ { "id": 48213, "at": "2026-09-10T09:02:11Z", "kind": "box.wake", "origin": "server", "actorKind": "cli", "actorUserId": "7Hq2…", "actorTokenId": "9b1e…", "boxId": "5d0a…", "detail": {} } ] } ``` `scope` is `workspace`, or `own` for a contractor (their boxes only, and `members` empty). A box's `idleForSec`, `cpuPct` and `memPct` are `null` unless it is running; `recent` is the newest 50 [audit events](#audit-log). What a session, today and the cycle mean is on [Teams: Team activity](https://prized.dev/docs/teams#team-activity). ## The edge [#the-edge] Routes below are relative to `$EDGE/v1/box/{box}/`. `POST /exec` and `POST /prompts` wake a suspended box; every other route answers `409 box_not_running` instead. Paths are read the way `prized cp` reads them: relative paths and `~/…` are under your home, absolute paths are taken as is, and every answer echoes the real path it touched. A successful answer carries `ok: true` and a `type`; a failed one carries a code from [Errors](#errors). ### Commands [#commands] | Route | Body or query | Answer | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `POST /exec` | `{command, cwd?, timeoutSeconds? (1 to 600, default 30), env? (up to 32), detached?}` | `200 exec.result`, below | | `POST /exec` with `detached: true` | the same | `200 exec.started`: `processId, pid, cwd, startedAt, logPath` | | `GET /exec` | | `200 exec.list`: `processes[]` of `processId, pid, running, lost, exitCode, signal, startedAt, command` | | `GET /exec/{processId}` | query `tail` (bytes; default 16384, max 1048576) | `200 exec.status`: the list fields plus `finishedAt, cwd, stdout, stderr, stdoutTruncated, stderrTruncated` | | `DELETE /exec/{processId}` | | `200 exec.killed`: `processId, wasRunning, signal` (`TERM`, or `KILL` when TERM was not enough) | | `GET /exec/{processId}/stream` | query `stdoutOffset`, `stderrOffset` (default 0, or the `Last-Event-ID` header as `:`), `streams` (`stdout`, `stderr`, or both), `encoding` (`utf8` or `base64`) | `200 text/event-stream`, below | The answer to the `make test` run in [Examples](#examples): ```json { "ok": true, "type": "exec.result", "exitCode": 0, "signal": "", "stdout": "ok \tapp/internal/api\t1.42s\n", "stderr": "", "stdoutTruncated": false, "stderrTruncated": false, "timedOut": false, "startedAt": "2026-09-10T17:20:01Z", "finishedAt": "2026-09-10T17:20:04Z", "cwd": "app" } ``` Commands run under `sh -c` as your box user, in your home or `cwd`; the answer's `cwd` is the one you sent, or `~`. `timedOut` means the time limit ended the command (exiting 124 by itself is `exitCode: 124, timedOut: false`). A synchronous run keeps the last 1 MiB per stream and sets its `Truncated` flag; for all of it, run detached and `/download` the log ([Run commands and files](https://prized.dev/docs/exec#detached-processes)). Both answers carry `commandId` when the run was recorded in the [command history](#command-history); for a detached run it equals `processId`. **The stream** follows one detached process from the given offsets. Each event's `data:` is one JSON object and its `id:` is `:` after it: | Event | `data` | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `attached` | `{processId, stdoutOffset, stderrOffset}`, first | | `stdout`, `stderr` | `{data, offset, nextOffset}`; `data` is base64 under `encoding=base64`. Under `utf8` invalid bytes become U+FFFD and a character is never split across events; offsets always count bytes | | `heartbeat` | `{}`, after 15 seconds without an event | | `reconnect` | `{stdoutOffset, stderrOffset}`, after 10 minutes; the stream ends, open it again from there | | `exit` | `{status, exitCode, signal, stdoutOffset, stderrOffset}`, last; `status` is `exited`, `killed`, or `lost` | | `error` | `{code, message}`; the stream ends | A stream holds one of the token's and the box's 8 concurrent requests while open, and a token holds at most 4 streams; a fifth answers `429 rate_limited`. ### Files [#files] | Route | Body or query | Answer | | --------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /files` | query `path` | `200 file.read`: `path, encoding (utf8 or base64), size, mode, content`; over 1 MiB answers `413` and points at `/download` | | `PUT /files` | `{path, content, encoding? (utf8 or base64), mode? ("0644")}` | `200 file.written`: `path, size` | | `GET /download` | query `path` | The file as `application/octet-stream` with `Content-Length`, or a directory as `application/x-tar` with `X-Prized-Content: directory`; `X-Prized-Path` carries the real path | | `PUT /upload` | query `path`, `mode?`; the raw file as the body | `200 file.uploaded`: `path, size` | Size caps are on [Limits: Commands and files](https://prized.dev/docs/limits#commands-and-files). ### Prompts and events [#prompts-and-events] | Route | Body or query | Answer | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `POST /prompts` | `{provider (claude, codex, opencode or pi), prompt (up to 512 KiB), model?, reasoningEffort? (low, medium, high), cwd?, continue?, queue?, auto?}`; a model of `amazon-bedrock/…`, `openrouter/…` or `deepseek/…` picks a [backend](https://prized.dev/docs/remote-agents#models-and-backends) | `202 prompt.started`: `run`; or `202 prompt.queued`: `queued, active` | | `GET /prompts` | | `200 prompt.list`: `runs[]` newest first (at most 50), `active` (the run in progress, or `null`) | | `GET /prompts?conversation=cv_…` | keeps to one conversation's runs | The same | | `GET /prompts/conversations` | | `200 prompt.conversations`: `conversations[]` most recently active first (at most 50), `running` (runs in progress), `limit` (the box's limit) | | `GET /prompts/{runId}` | | `200 prompt.status`: `run` with the full `prompt`, `stderr` (last 4 KiB), `stderrTruncated` | | `GET /prompts/{runId}/events` | query `after` (a `seq`, default 0), `limit` (1 to 1000, default 200; a page also stops at 1 MiB) | `200 prompt.events`: `run, events[], next, finished`; pass `next` as the following `after` | | `GET /prompts/{runId}/events?follow=1` | or `Accept: text/event-stream` | Server-Sent Events: `event: prompt` per event, `event: done` with `{run, next}` when the run has ended, `event: error` with `{code, message}` if the box stops answering, a `: keep-alive` comment while nothing happens | | `GET /prompts/events?follow=1` | or `Accept: text/event-stream`; query `conversation` to keep to one | Server-Sent Events of every run in progress, and of each that starts meanwhile, until none is left (with none in progress, the newest run): `event: run` with `{run}` as a run starts, `event: prompt` per event with its `runId` and `conversationId`, `event: run.done` with `{run, next}`, then `event: done`; `event: error` with `{code, message}` when a run disappears from the box (the stream goes on) or the box stops answering (the stream ends) | | `POST /prompts/{runId}/interrupt` | | `200 prompt.interrupted`: `id, wasRunning, signal` | | `POST /prompts/interrupt` | query `conversation` to stop only that conversation's run | `200 prompt.interrupted`: `interrupted[]` (`{id, conversationId, signal}` for every run stopped), and `id, wasRunning, signal` of the first | A run is `{id, provider, model, reasoningEffort, auto, status (running, done, failed, interrupted), exitCode, pid, cwd, startedAt, finishedAt, sessionId, resumedFrom, parent, eventCount, prompt}`; an event is `{seq, at, type, text, tool: {name, input}, raw}` ([JSON and JSON Lines](https://prized.dev/docs/remote-agents#json-and-json-lines)). A stream holds one of the token's concurrent request slots until the run ends; approvals, continuity, the queue and the run directory are on [Prompt an agent remotely](https://prized.dev/docs/remote-agents). Conversations: `POST /prompts` also takes `new` (a new conversation) or `conversationId` (continue that one), at most one of them and `continue`, and answers `conversationId` in the `run` and in `prompt.queued`; a run also carries `conversationId`. Without `new`, `conversationId` or `continue` a prompt starts a new conversation, unless `queue` is set and a run of the same provider is in progress, which it then follows. A conversation is `{id, provider, model, prompts, queued, running, lastRunId, lastStatus, sessionId, lastPrompt, createdAt, updatedAt}` ([Conversations](https://prized.dev/docs/remote-agents#conversations)). Every provider, OpenCode and pi included, and every backend model work in conversations alike. ### Desktop [#desktop] | Route | Body | Answer | | -------------------- | ------------------------------------------------ | ----------------------------------------------------- | | `POST /desktop/stop` | `{mode?}` (`desktop`, the default, or `browser`) | `200 {ok: true, box, mode}`; never wakes a paused box | Its errors use the bare `{"error": "code"}` form: `unauthorized` (a `dcp_` token only), `token_expired`, `not_found`, `bad_request`, `request_timeout` (`408` if the request body takes too long to arrive), `box_not_running`, `box_unreachable`, `desktop_stop_failed`, `resolve_failed`, `control_plane_unavailable` and `rate_limited` (`429` with `Retry-After`; a stop counts against the edge's per-token and per-box limits). The desktop stream itself is a dashboard WebSocket; from a script, `prized desktop --no-open` prints a `vnc://` address and the password ([Desktop](https://prized.dev/docs/desktop)). ### Errors [#errors] Every edge route but `POST /desktop/stop` fails as `{"ok": false, "error": {"code", "message"}}` with one of these codes: | HTTP | `error.code` | Meaning | | ---- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 401 | `unauthorized` | No bearer, a token the control plane rejected, or another class of credential (a monitor ticket, a phone password) | | 401 | `token_expired` | The API key has expired: make a new one ([When a key expires](https://prized.dev/docs/workspaces#when-a-key-expires)) | | 404 | `not_found` | No such box on this account (or outside the key's boxes), process, run, path (or parent directory), or route | | 405 | `method_not_allowed` | A known route with the wrong method; `Allow` lists the right ones | | 409 | `box_not_running` | Any route but `POST /exec` and `POST /prompts` on a suspended box; the message says how to wake it | | 400 | `invalid_request` | Not the documented shape: bad JSON, an empty command, a bad env name or mode, a bad `model`, a `reasoningEffort` outside low/medium/high, a newline in `cwd` | | 400 | `invalid_timeout` | `timeoutSeconds` outside 1 to 600 | | 400 | `invalid_path` | A path with a NUL or newline byte, a directory where a file was expected, or no path | | 403 | `permission_denied` | Your box user cannot read or write that path | | 413 | `payload_too_large` | Over the inline (1 MiB) or transfer (1 GiB) cap | | 429 | `rate_limited` | Too many requests for this token or box; `Retry-After` says when | | 502 | `box_unreachable` | The box could not be reached, or did not wake in time | | 502 | `exec_failed` | The ssh session or the on-box script failed; the message carries the box's own words | | 5xx | `control_plane_unavailable` | The control plane could not be reached to check the token | The prompt routes add: | HTTP | `error.code` | Meaning | | ---- | ------------------------ | ------------------------------------------------------------------------------------------------------------- | | 400 | `invalid_provider` | `provider` is not `claude`, `codex`, `opencode` or `pi` | | 400 | `prompt_required` | `prompt` is missing or blank | | 409 | `prompt_in_progress` | A run is in progress; the message names it. Queue behind it, or interrupt it | | 409 | `provider_not_signed_in` | The provider, or the model's backend, has no credential on the box; the message names what to set | | 409 | `provider_not_installed` | The provider's CLI is not on the box's login PATH, or installing OpenCode or pi failed (the message says why) | | 409 | `no_session_to_continue` | `continue: true` with no earlier run of the provider that recorded a session | | 409 | `prompt_limit_reached` | The box has as many runs in progress as its limit ([Limits](https://prized.dev/docs/limits#prompting-an-agent-remotely)) | | 409 | `provider_mismatch` | `conversationId` names a conversation of another provider | | 404 | `not_found` | `conversationId` names no conversation on the box | ## Examples [#examples] Create a box and wait for it to run: ```bash BOX=$(curl -s -X POST "$API/boxes" -H "$AUTH" -H "Content-Type: application/json" -d '{"name":"ci-42","tier":"flow","ttlMinutes":120}' | jq -r .box.id) until [ "$(curl -s "$API/boxes/$BOX" -H "$AUTH" | jq -r .box.observedState)" = running ]; do sleep 5; done ``` Run a command on it, upload a file, download a directory: ```bash curl -s -X POST "$EDGE/v1/box/ci-42/exec" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"make test","cwd":"app","timeoutSeconds":300}' | jq '{exitCode, timedOut}' curl -s -X PUT "$EDGE/v1/box/ci-42/upload?path=data/train.bin" -H "$AUTH" --data-binary @train.bin curl -s "$EDGE/v1/box/ci-42/download?path=app/dist" -H "$AUTH" -o dist.tar ``` Prompt the agent and follow it (each prompt starts a new conversation unless it names one; `run.conversationId` is the id to pass as `conversationId` next time, and a prompt for a conversation with a run in progress answers `prompt_in_progress` unless it passes `queue: true`): ```bash RUN=$(curl -s -X POST "$EDGE/v1/box/ci-42/prompts" -H "$AUTH" -H "Content-Type: application/json" -d '{"provider":"claude","prompt":"fix the failing tests in app/ and run them again","cwd":"app","auto":true}' | jq -r .run.id) curl -sN "$EDGE/v1/box/ci-42/prompts/$RUN/events" -H "$AUTH" -H "Accept: text/event-stream" ``` Delete the box and wait for the operation: ```bash OP=$(curl -s -X DELETE "$API/boxes/$BOX" -H "$AUTH" | jq -r .operation.id) until [ "$(curl -s "$API/operations/$OP" -H "$AUTH" | jq -r .operation.status)" = completed ]; do sleep 5; done ``` ## Not yet [#not-yet] | Not yet | Instead | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | An SDK or an OpenAPI document | The routes above with `curl` or any HTTP client, or the CLI's `--json` output ([CLI: For agents and scripts](https://prized.dev/docs/cli#for-agents-and-scripts)) | | Webhooks | Poll `GET /api/v1/boxes/{id}` or `GET /api/v1/operations/{id}`; the push channels are the prompt event stream and the [audit export](https://prized.dev/docs/teams#scheduled-export) (audit rows only) | --- # Troubleshooting > Symptoms and their fixes, starting with the one command that checks and repairs config, auth, tunnel, SSH, sync, the daemon, and the box agent. URL: https://prized.dev/docs/troubleshooting If Prized itself looks down, check [prized.dev/status](https://prized.dev/status) first: it shows whether the website and dashboard, the API and the browser terminal are up, and any incidents. ## Run the doctor [#run-the-doctor] ```bash prized doctor ``` It repairs what it can (a missing `~/Prized`, Mutagen not fetched, a stopped daemon, a missing SSH `Include`, a box agent behind your CLI) and marks each `(fixed)`. Passing checks print nothing; each failure prints its remedy. `--no-fix` only reports; `-v` prints every check; `--bundle` writes a diagnostics tarball for a bug report, and nothing leaves your machine. ## Editor or ssh says the box is not running [#editor-or-ssh-says-the-box-is-not-running] The box is asleep. Wake it, then reconnect: ```bash prized box resume BOX --wait ``` * **Why:** it paused itself after sitting idle (2 hours by default on the [free credit](https://prized.dev/docs/billing#free-credit)), or it was paused. Plain `ssh`, VS Code or Cursor Remote-SSH, and sync do not wake it; they fail with `box not running (state "suspended")` in their output or log. * **Other ways to wake it:** `prized ssh`, `prized exec`, `prized up`, or **Resume** on the dashboard ([Pause and resume](https://prized.dev/docs/boxes#pause-and-resume)). * **Sync** retries by itself and catches up once the box runs. * **Change or stop the sleep:** `prized box auto-pause BOX 4h` (30m to 7d) or `prized box auto-pause BOX off`, or **Auto-pause** on the box's dashboard page. A box on the free credit never sleeps in its first 24 hours. ## Box and daemon [#box-and-daemon] | Symptom | Try | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Box shows unreachable | `prized ssh` wakes a suspended box. Still down? `prized box info` or the dashboard shows its state and events. | | `BOX is paused because your free credit ran out` (or `your credit ran out`) from `ssh `, an editor, sync, `prized ssh` or `prized up` | The balance is empty, so no wake can start the box. Nothing was deleted: its disk is kept. Pick a plan or add credit at the link the message prints ([Running out](https://prized.dev/docs/billing#running-out)); boxes the stop paused start again on their own, and editors and sync reconnect once the box runs. | | The browser terminal, files, desktop, a shared port or the phone says `Prized could not reach BOX from its own servers` | Not your network; usually the box is still starting. Reload in a minute, and email the support address shown on https://prized.dev/docs/troubleshooting if it persists. | | `open terminal failed: missing or unsuitable terminal: xterm-ghostty` from `prized ssh`, or `unknown terminal type` inside a `--raw` shell (Ghostty) | A box adds Ghostty's terminal entry within about six hours of running (new boxes have it); meanwhile, `TERM=xterm-256color prized ssh ` connects. | | Cmd-C copies nothing out of Claude Code | Claude handles the mouse: select, press **c**, and through `prized ssh` the text lands on your clipboard. Or drag holding **Fn** (Apple Terminal), **Option** (iTerm2) or **Shift**, then Cmd-C ([Copy out](https://prized.dev/docs/sessions#copy-out)). | | Box paused by itself | The last event in `prized box info` says why: `reconciler:auto_pause` (idle) or `reconciler:pause_deadline`. Turn them off: `prized box auto-pause BOX off`, `prized box extend BOX --no-deadline` ([Boxes](https://prized.dev/docs/boxes#auto-pause)). On the [free credit](https://prized.dev/docs/billing#free-credit), a box pauses after 2 hours idle unless you changed that, never in its first 24 hours. | | Editor or `ssh` cannot connect: box not running | The box is paused: `prized box resume BOX --wait`, then reconnect ([Editor or ssh says the box is not running](#editor-or-ssh-says-the-box-is-not-running)). | | `ssh: Could not resolve hostname ` from `ssh ` or a sync session | Your ssh skipped the `Include` atop `~/.ssh/config` that loads the stanza prized writes (`~/.ssh/config.d/prized`). `prized doctor` says why and fixes what it can: the line missing (an old `Include config.d/doppel` does not count; `prized doctor --fix` adds the right one), a `Host`/`Match` block above it, or an `ssh` alias, wrapper, or HOME not your account's. Meanwhile `prized setup`, `prized ssh` and `prized exec` work; `ssh ` and sync need the Include. | | `"" is not the box this machine knew by that name` (from `ssh ` or a prized command) | The name moved to another box (yours was recreated, or its freed name taken). If expected, `prized box rebind ` binds the alias to the box that has the name now; otherwise check `prized box ls`. Until you rebind, connections under the name are refused and a running `prized proxy` stops. `could not verify that "" is still box …`: you are signed out, or Prized stayed unreachable (offline; right after a wake, the connection first waits a few seconds for the network); `prized login`, or retry once online. | | Daemon not running | `prized daemon restart`; `prized daemon logs` if it will not stay up. Before your first `prized setup` there is no daemon: setup installs it, as does `prized daemon install`. | | Daemon gone after a reboot on Linux without systemd (WSL, a container) | With no systemd user session, prized runs the daemon itself and nothing brings it back: `prized doctor` starts it again. On WSL, turning systemd on (`systemd=true` under `[boot]` in `/etc/wsl.conf`, then `wsl --shutdown`) and running `prized daemon install` makes it start on its own. | | `prized setup` or `daemon install` says the daemon is installed for your login session and this terminal is not in it | This shell (ssh, or a remote tool's terminal) is outside the macOS login session the daemon was installed from: run the command from Terminal on the Mac itself. | | "the running daemon serves a different box" | You switched the default (`prized init --force`) while the old daemon ran, or aimed `--config` at another box's file: `prized daemon restart`. | | doctor warns the daemon serves another box | `--config` drives another box without port mirrors. Connect it with `prized setup --box NAME`, then use `--box NAME`. | | doctor reports an unpinned SSH bridge | An earlier release wrote the stanza: `prized doctor --fix`, `prized login`, or `prized setup` rewrites it. | | `prized setup`, `prized up` or `prized ssh` says `box "OLD" is terminated` or `no box "OLD" on your account`, after you deleted that box or signed in to another account | This machine is still set up for the old box, and `prized login` and `prized setup` keep an existing config. If `--box OLD` or `PRIZED_BOX` names the old box, drop or unset it. With no box left, `prized box new` first. `prized init --force --box NAME` makes one of your boxes this machine's default (the old config is backed up first); then run `prized setup` in your project again. If the failing command names its config with `--config FILE` or `PRIZED_CONFIG`, switch that file instead: `prized --config FILE init --force --box NAME`. A box's own `boxes/OLD.toml` can't be moved to another box; use the default config or NAME's own instead. A box already connected here with its own config needs no switch: run the command the hint prints for it (usually `prized ssh NAME` or `prized setup --box NAME`; `--config PATH` when its config is somewhere `--box` doesn't look). | | `no config for box NAME` | Connect the box here first: `prized setup --box NAME` (`prized ssh NAME` needs no config). Misspelled? `prized box ls` has the right name. | | `session_space_exhausted` or `customer_session_limit` when connecting to a box | No room on the box for another connection from your machine; older releases held closed ones for hours. Run `prized update`; still refused, email the support address shown on https://prized.dev/docs/troubleshooting. | | `Connection closed by UNKNOWN port 65535` from `ssh ` or `prized ssh` | The Prized bridge stopped before connecting; the `prized` line above says why (signed out, box unreachable, no network path, name changed hands). `prized doctor` checks the chain. | | doctor fails `agent-version` | Agent behind the CLI: `prized doctor` updates it (a new box catches up by itself within a minute or two). `cli behind the agent`: `prized update`. | | Codex on the box says `Your access token could not be refreshed because your refresh token was already used` | The box shared your machine's ChatGPT sign-in until your machine refreshed it. Run `codex logout` on the box, then `prized agents handoff codex` here (it opens the sign-in page), or `codex login --device-auth` on the box. | | The phone connection closes during sign-in | The gateway may be busy. Wait briefly and reconnect with the same phone password. If it keeps failing, check the host, port, username and current password in the box’s Phone access settings ([From your phone](https://prized.dev/docs/codex#from-your-phone)). | | Claude Desktop (or another SSH app) asks for a password | The box lacks this machine's device key: run `prized login` (or one `prized ssh`) here, wait a minute, retry ([Claude Desktop remote](https://prized.dev/docs/claude-desktop)). | | Claude Desktop connects but its terminal reports `PTY allocation request failed` | Quit Claude Desktop fully, reopen it, and reconnect. Still failing? Contact support ([Claude Desktop remote](https://prized.dev/docs/claude-desktop)). | ## Networks that block the tunnel [#networks-that-block-the-tunnel] The CLI reaches a box over WireGuard (UDP 51820), over the same tunnel through the Prized relay (HTTPS to `term.prized.dev`), or over HTTPS to the box's own address. When UDP does not answer within a few seconds, it keeps whichever path answers first (`box.transport_path` in `prized status --json`) and returns to UDP once it can. `prized status`, `prized doctor`, `prized setup`, or any command that connects retries every path at once. | Symptom | Try | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `box-dial` fails with `no network path to the box` (and `prized setup` stops there), or `prized exec`, `prized ssh` or `prized cp` stops with `cannot reach BOX: no network path to the box` | Every path was refused. The hint says what refused each one, including the proxy used, and what IT must allow: outbound UDP 51820 to the box's address, or WebSocket connections to `term.prized.dev` on port 443 without TLS inspection. Forward it as is. | | The hint says `the Prized relay itself refused`, or `a problem on Prized's side, not your network` | Not your network; nothing to ask IT. Retry in a few minutes, then email the support address shown on https://prized.dev/docs/troubleshooting; the code in brackets says why. | | The relay refused with `relay_busy` | The relay is full, for your workspace or overall. Retry in a minute; if a large team keeps hitting it, email the support address shown on https://prized.dev/docs/troubleshooting. | | The relay refused with `relay_lease_ended` or `relay_ticket_expired` | The tunnel ended, or this device was revoked. Run the command again, after `prized login` if the device was revoked. | | `box-dial` fails with `no WireGuard reply` | The tunnel got through, but nothing answered; usually the box is still starting or resuming. Retry, then email the support address shown on https://prized.dev/docs/troubleshooting. Behind TLS inspection, ask IT whether WebSocket traffic passes once connected. | | `cannot reach Prized at api.prized.dev` (from any command, `prized setup`, or doctor's `cp-auth`) | Your network, not your sign-in: Prized itself did not answer. Ask IT for HTTPS to `api.prized.dev` on port 443; the hint names the proxy used and why. | | Your network reaches the web only through a proxy | A Mac system proxy (or an IT profile) needs nothing. Otherwise set `HTTPS_PROXY` (and `NO_PROXY` for exceptions) and run `prized setup` ([Proxy settings](#proxy-settings)). | | `warning: proxy: the background service keeps the proxy recorded earlier` (from `prized setup`, `prized login` or a daemon command), or doctor's `daemon-proxy` warns that this shell's proxy `did not answer` | This shell's `HTTPS_PROXY` does not answer here, so the background service keeps its recorded proxy. Correct or unset `HTTPS_PROXY`; if the recorded one is stale, rerun `prized setup` where the new proxy answers, or `prized daemon proxy set URL` (it records even a proxy that does not answer from here, with a warning). | | Your computer finds its proxy automatically, or the proxy signs in with your Windows account | Proxy auto-config (PAC) files, Windows proxy sign-in (NTLM, Kerberos), and the Windows system proxy are not supported; the hint says which it found. Diagnostics never show a PAC location (it can hold sign-in tokens). Put the proxy's address in `HTTPS_PROXY` and run `prized setup`, or ask IT to let `prized.dev` and `term.prized.dev` through without a sign-in. | | A CLI before 0.17.0 fails with `expected handshake response status code 101 but got 403` | Those versions fall back only to the box's own HTTPS address, which your network refused. Run `prized update`; still before 0.17.0 in `prized version`? `prized update --channel edge`. | ### Proxy settings [#proxy-settings] * **What uses it:** the CLI, daemon, sync, and editor SSH. After `prized setup`, the daemon, sync, and editor SSH keep the proxy from any shell, going direct while it is unreachable. * **Precedence:** `HTTPS_PROXY`, then the proxy `prized setup` recorded, then the Mac's. A shell proxy replaces the recorded one only if it reaches Prized (the `keeps the proxy recorded earlier` warning above). * **See or change it:** `prized daemon proxy` shows the daemon's; `prized daemon proxy set URL` and `prized daemon proxy clear` change it. * **Ignored, with a note:** `HTTP_PROXY` alone, and values that are not a proxy address (a PAC file's URL, `socks://`); the CLI keeps the proxy it had. * **Formats:** SOCKS5 as `socks5://host:port`; IPv6 in brackets; a proxy sign-in goes in the URL: `http://user:password@proxy:port`. ## Ports and sync [#ports-and-sync] | Symptom | Try | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | A port is not on localhost | `prized ports ls` shows every listener and why one is not mirrored; a port you toggled off stays off until toggled back. | | Sync seems stuck | A first edit in a folder the box has not touched lately can take 10 s. Then `prized sync ls`; `prized sync flush` forces a cycle; `prized sync repair myproject` for a halted session. | | Sync shows `error` | The reason is under the table, and the daemon retries. Fix the cause (or `prized sync rm` the project) and re-run `prized setup`. | | Every project is `paused` and you did not pause it | Your machine ran low on disk ([Sync](https://prized.dev/docs/sync#low-disk-on-your-machine)); the line under the table has the figure. Free space or ignore folders that should not travel, then `prized sync resume --all`. | | `prized setup` says the project step failed, but the box is fine | `prized sync ls` names the cause. If the sync could not be created, re-run `prized setup` the same way: it tries again at once. A halted project needs `prized sync repair myproject`, a paused one `prized sync resume myproject`. | | Mutagen reported missing | `prized doctor` or `prized mutagen ensure` fetches Prized's managed copy. If your network cannot download it, the error names the file: download it in a browser, then run `prized mutagen ensure --from ` and re-run `prized setup`. | ## CLI and updates [#cli-and-updates] | Symptom | Try | | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | CLI feels outdated | It [updates itself](https://prized.dev/docs/cli#updates); `prized update` installs the newest release now. | | `prized version` warns the daemon version differs | An update landed while the daemon was in use; `prized daemon restart` applies it. | | Every command exits 5 with an update hint | Your CLI is older than the API supports: run `prized update` once. | | `this API key has expired` (exit 8), or `401 token_expired` from the API or the edge | The key ran out. On a machine (its key expires after 180 days unused), run `prized login`. A key from **New key**: make a new one under **Settings → API keys** and replace it where it runs ([When a key expires](https://prized.dev/docs/workspaces#when-a-key-expires)). | | A script's key answers `404 not_found` for a box the dashboard shows, or `403 key_access_denied` with `scope: "boxes"` | The key is limited to other boxes, or the route reaches the whole workspace ([Limit a key to some boxes](https://prized.dev/docs/workspaces#limit-a-key-to-some-boxes)). Make a key for that box, or one without a box limit. | | `prized: command not found` right after installing | `~/.local/bin` is not on this shell's `PATH` yet: `source` the file the installer's last line names, or open a new terminal. Windows: see [Windows](#windows). | | `prized: command not found` on an older install | Re-run the [install script](https://prized.dev/docs/quickstart); it installs `prized` and keeps `doppel` as an alias. | | macOS asks again for folder access after an update | `prized update` to the latest release and approve once more; macOS remembers it from then on. | | A Go tool is `not carried` after setup | The reason is in the env row. One the box could not download (a private module, or code that was never pushed): build it on the box from a checkout of its repo with `go install ./...`. One that does not build on Linux: use a Linux build of it, or an alternative. | | An environment item fails during setup but installs fine by hand | Re-run with `prized setup --env-workers 1` (one item at a time, in order) and `--verbose` ([Bring your environment](https://prized.dev/docs/environment)). | | `ssh: not found in a trusted system directory` | Prized runs ssh only from system directories (`/usr/bin`; `System32\OpenSSH` on Windows), never from `PATH`. For another ssh, set `bin = "/path/to/ssh"` under `[ssh]` in `~/.config/prized/config.toml`. | | `docker: not found in a trusted location` (or `pg_dump`, `gzip`) during the data step | Data-step helpers run only from system and package-manager directories, never from `PATH`: install the tool there, or answer no to the data group and copy by hand. | ## Windows [#windows] | Symptom | Try | | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `prized` is not recognized right after installing | A window opened before the install lacks the new `PATH` entry. Open a new terminal, or run `$env:Path = "$env:LOCALAPPDATA\Prized\bin;$env:Path"` in that one. | | `prized ssh` says ssh is missing | Turn on Settings, Optional features, OpenSSH Client, or run `Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0` in an elevated PowerShell. | | `WARNING: UNPROTECTED PRIVATE KEY FILE`, or "permissions are too open" | Windows OpenSSH refuses a key another account can read, usually after a tool (Codex's sandbox) gave a group access under `%LOCALAPPDATA%`. `prized doctor` or the next `prized login` cuts `%LOCALAPPDATA%\Prized\id_prized` back to your account, SYSTEM, and Administrators. | | SmartScreen or Defender warns about `prized.exe` | The binaries are not code-signed: choose More info, Run anyway. If Defender quarantines it, restore it, re-run the installer, and email the support address shown on https://prized.dev/docs/troubleshooting. | | The daemon is not running, or dies after logout | `prized daemon install` re-registers and starts the `PrizedDaemon` task, which runs only while you are signed in; `prized daemon logs` has its log. | | `prized daemon install` fails with an access error | A machine policy blocks Task Scheduler for users. Run `prizedd run` in a terminal you keep open, and tell the support address shown on https://prized.dev/docs/troubleshooting. | | `prized mosh`, `mount`, or `env` refuses | Not on Windows yet ([CLI](https://prized.dev/docs/cli#windows)); `prized ssh`, sync, ports, `exec`, and `cp` cover the same ground. | ## Sandboxes [#sandboxes] | Symptom | Try | | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `429 sandbox_quota_exceeded` or `422 invalid_resources` with `"tier": "free"` | A [free-tier](https://prized.dev/docs/sandboxes#free-tier) limit: 4 sandboxes, 2 vCPU and 4 GB each, 60 minutes. A [top-up](https://prized.dev/docs/billing#topping-up) or a plan lifts the workspace to the full quotas. | | `409 paid_balance_required` on a create, resume, or fork | Free-tier access is switched off for your workspace, and it has only free credit. Start a plan or buy a [top-up](https://prized.dev/docs/billing#topping-up), then retry. | | `409 insufficient_balance` | The balance is empty: add credit. Sandboxes that paused when it ran out stay paused until you resume them. | | `409 workspace_held` | The workspace is on hold, and its running sandboxes and boxes were paused; the `message` says why ([Not allowed](https://prized.dev/docs/limits#not-allowed)). Pausing and deleting still work. Mistaken? Email the support address shown on https://prized.dev/docs/troubleshooting. | | `409 workspace_closing` | The workspace is being closed. The owner can reopen it ([Close or erase the workspace](https://prized.dev/docs/workspaces#close-or-erase-the-workspace)). | | `429 sandbox_quota_exceeded` | `reason` names the quota. Paused sandboxes hold their vCPU and memory, so delete the ones you are done with ([Limits: Sandboxes](https://prized.dev/docs/limits#sandboxes)). | | `403 key_access_denied` | The key lacks **Sandboxes: Write** (or **Read** for lists), or is limited to some boxes. Make a key with sandbox access under **Settings → API keys**. | | `404 not_found` on every sandbox route | The key belongs to a contractor, or to another workspace. | | `409 sandbox_not_running` from a command or file request | Read the sandbox's `state`. `resizing`: the resize ends on its own; retry once it leaves `resizing`. `error`: it will not run again, and `errorReason` says why; delete it and create another. `deleting`: it is going away. `paused`, `starting`, or `pausing`: the request sent `Prized-Auto-Resume: false`, which answers 409 whenever the sandbox is not `running`. Drop the header and the request waits for it, waking a paused one ([Pause and resume](https://prized.dev/docs/sandboxes#pause-and-resume)); or keep it, resume a paused one yourself, and retry once the sandbox reads `running`. | | `503 sandbox_not_ready` from a command or file request | The sandbox was paused or starting and did not reach `running` within the edge's 10-second wait ([The edge](https://prized.dev/docs/sandboxes-api#the-edge)). Nothing ran; retry. | | `502 sandbox_unreachable` | The sandbox did not answer: usually it had just started or resumed. Retry, but a command that got this may already have run. | | A sandbox sits in `starting` | Capacity is being added at a busy moment; it usually clears by itself. If it ends in `error`, delete it and create another. | | A sandbox is in `error` | `errorReason` says why. It costs nothing and is removed after `autoDeleteMin`, or an hour; create another. | | A sandbox is gone | It reached its `maxRuntimeMin`, or `autoDeleteMin` after a pause; both erase it ([Automatic pause and delete](https://prized.dev/docs/sandboxes#automatic-pause-and-delete)). | | `pip install` or `npm install -g` says permission denied | The sandbox user has no root. Use a venv (`python3 -m venv .venv`), or `npm config set prefix ~/.local` first ([Inside a sandbox](https://prized.dev/docs/sandboxes#inside-a-sandbox)). | | `409 fork_capacity` | The parent's machine has room for only `fits` more copies, and how many fit depends on the shape ([Fork](https://prized.dev/docs/sandboxes#fork)). Fork that many or fewer if `fits` is above 0, try again later, or start a new sandbox and fork that, keeping the parent until the new one runs: new sandboxes start, when they can, where there is room to fork them. The new sandbox and each copy count toward your quota (the [free tier](https://prized.dev/docs/sandboxes#free-tier) allows 4 at once): at the limit, delete sandboxes you no longer need and wait until `prized sandbox ls` no longer lists them, or raise it: on the free tier by topping up, otherwise by emailing the support address shown on https://prized.dev/docs/troubleshooting. Copy out what you need before you delete one, and never delete a parent while a copy forked from it is still `starting`. | | `503 sandbox_capacity` | No sandbox capacity is free right now; nothing was created or billed. Retry after the `Retry-After` seconds. | ## Older installs [#older-installs] Configs that still point at `doppel.prized.dev` or `api.doppel.prized.dev` keep working; page requests redirect to `prized.dev`. ## Account [#account] | Symptom | Try | | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Sign-in fails with "unable to link account" | Your identity provider reports a different account for this address than Prized knows (mailbox deleted, recreated, or reassigned): email the support address shown on https://prized.dev/docs/troubleshooting. | | Every box paused and the dashboard says the workspace is suspended or paused | The message says why. On the free credit, one process holding the whole box at full CPU for over 30 minutes pauses the workspace: start a plan and wake the box. Mining: [Not allowed](https://prized.dev/docs/limits#not-allowed). Mistaken? Email the support address shown on https://prized.dev/docs/troubleshooting. | | A work address that had a Prized account now starts empty | Expected if the address was reassigned to you: accounts follow your identity, not the mailbox, so you never inherit the previous holder's. | ## Still stuck [#still-stuck] Email the support address shown on https://prized.dev/docs/troubleshooting with the doctor output. --- # Changelog > What changed in each release of the prized CLI and box agent, newest first. URL: https://prized.dev/docs/changelog The dashboard, the API, and the site update continuously; this page lists the releases of the `prized` CLI, its daemon, and the box agent. The CLI updates itself ([Updates](https://prized.dev/docs/cli#updates)); to update now: ```bash prized update ``` `prized version` shows the client, daemon, and box agent versions in use. ## Templates removed [#templates-removed] Released 2026-10-18. * Templates are gone from the dashboard, the CLI, and the API. Each existing template is now a plain manual snapshot under its own snapshot name, disk kept; start a box from it with `prized snapshot restore `, or **Restore into a new box** on the box's Snapshots tab (under **Deleted**, in Snapshots, when that box is gone) ([Snapshots](https://prized.dev/docs/snapshots#restore-into-a-new-box)). * The template API routes and `fromTemplate` on box create answer `410 templates_removed` ([API reference](https://prized.dev/docs/api#snapshots)); an earlier CLI's `snapshot template` gets that answer, and its `snapshot untemplate` reports that no template has the name. An old dashboard link to start a box from a template now says templates were removed instead of starting a fresh box. ## 0.21.1 [#0211] Released 2026-10-14. * A box on the [free credit](https://prized.dev/docs/billing#free-credit) pauses itself after 2 hours idle, never in its first 24 hours. `prized box auto-pause BOX off` or `prized box new --auto-pause off` turns it off ([Auto-pause](https://prized.dev/docs/boxes#auto-pause)). * Auto-pause counts sustained network traffic, like a sync, a clone or download, or a container image pull, as use. * Plain `ssh `, an editor's remote session, and sync say when a box is paused because the credit ran out, with the link to Billing, instead of a connection error. `prized status` links to Billing once the credit is used up ([Running out](https://prized.dev/docs/billing#running-out)). * On Linux with no systemd user session (WSL without systemd, a container), `prized setup`, `prized up`, and `prized doctor` start the daemon themselves. Nothing restarts it after a reboot until one of them runs again. - `prized sandbox fork` on a full machine suggests starting a new sandbox and forking that, instead of trying again shortly. ## 0.21.0 [#0210] Released 2026-10-14. * `prized dashboard` (aliases `dash`, `top`) shows your boxes full screen in the terminal, with live charts, processes, ports, and sessions, and opens a shell on the box you pick ([Terminal dashboard](https://prized.dev/docs/cli#terminal-dashboard)). * `prized exec history` lists the commands run on a box and `prized exec logs` prints one's output, even while the box is paused; `--follow` streams a detached command as it runs ([Command history](https://prized.dev/docs/exec#command-history)). * `prized prompt` runs several prompts on one box at once, each in a conversation you continue with `--conversation`; `prized prompt conversations` lists them ([Conversations](https://prized.dev/docs/remote-agents#conversations)). * OpenCode and pi join Claude Code and Codex in `prized prompt` and `prized agents run`, installed on the box the first time you use them ([OpenCode and pi](https://prized.dev/docs/agents#opencode-and-pi)). * `prized prompt --model` takes a backend prefix to run Claude Code or Codex on another model provider with your own key, and `prized prompt models` lists the known models ([Models and backends](https://prized.dev/docs/remote-agents#models-and-backends)). * `prized ports share` makes links for people outside your workspace: `--anyone` for whoever holds the link, `--public` for everyone, each lasting as long as `--expires` says ([Share a port](https://prized.dev/docs/ports#share-a-port)). * `prized box new --region` and `prized box move` take the new regions by name: `virginia`, `ohio`, and `frankfurt` ([Regions](https://prized.dev/docs/machines#regions)). * A command run with an expired API key says `this API key has expired`, and `prized login --token` prints when a key expires and how many boxes it reaches ([When a key expires](https://prized.dev/docs/workspaces#when-a-key-expires)). * Ports mirrored to this machine come back within seconds after the daemon restarts or updates. - `prized sandbox` (alias `prized sbx`) drives [sandboxes](https://prized.dev/docs/sandboxes) from the terminal: create one, run commands in it, copy files in and out, pause, resume, fork, and delete, with your login or an API key in `PRIZED_API_KEY` ([CLI: Sandboxes](https://prized.dev/docs/cli#sandboxes)). ## Sandboxes (beta) [#sandboxes-beta] Released 2026-10-14. * [Sandboxes](https://prized.dev/docs/sandboxes): Linux microVMs your code creates through the API or the CLI, free to try on the free credit within the [free tier](https://prized.dev/docs/sandboxes#free-tier). Each boots from a template in seconds, runs commands and moves files, pauses for the price of its storage, and forks into many live copies, billed per second against the same balance as your boxes ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)). * Two templates: `prized-base-v1` (Ubuntu, Node.js, Python, git) and `prized-agent-v1` (with Claude Code and Codex). * The [Sandbox API](https://prized.dev/docs/sandboxes-api), the dashboard's **Sandboxes** page, and `prized sandbox` (alias `prized sbx`) in the [CLI](https://prized.dev/docs/cli#sandboxes) from v0.21.0. * API keys gain a **Sandboxes** access area ([What a key can do](https://prized.dev/docs/workspaces#what-a-key-can-do)). ## 0.20.0 [#0200] Released 2026-10-07. * `prized herdr` lands in [herdr](https://prized.dev/docs/herdr) instead of tmux: a terminal multiplexer that shows which of your coding agents is working, waiting on you, or done, installed on the box the first time you connect. * In `prized herdr` on macOS and Linux, an agent that finishes or needs you posts a notification on your computer ([Find the agent that needs you](https://prized.dev/docs/herdr#find-the-agent-that-needs-you)). * On macOS and Linux, a `prized ssh` session that loses the network reconnects on its own, back in the same session, and a dropped connection leaves your terminal clean ([When the connection drops](https://prized.dev/docs/sessions#when-the-connection-drops)). * On macOS and Linux, back-to-back `prized exec` runs and one-off `prized ssh` commands share one connection to the box, so each run after the first starts sooner. * Commands that wake a paused box reach it sooner. * `prized setup` on Windows syncs the folder it runs in, and `prized setup .` works on every system. * `prized setup` and `prized agents handoff codex` leave this machine's ChatGPT sign-in where it is and sign Codex in on the box through your browser, so neither side gets signed out ([Credentials](https://prized.dev/docs/credentials)). * `prized fork --wait` waits out the slow first copy of a big disk, and a command that wakes a box whose launch failed stops at once with the reason ([Fork a box](https://prized.dev/docs/snapshots#fork-a-box)). * `prized status` says whether boxes keep running when credit runs low: overage, a renewal being collected, or a failed renewal's seven-day grace ([Failed payments](https://prized.dev/docs/billing#failed-payments)). ## 0.19.0 [#0190] Released 2026-10-01. * Commands start faster and make fewer round trips: `prized ssh`, `exec`, and `mosh` look a box up once instead of twice, and the binary is smaller. ## 0.18.1 [#0181] Released 2026-10-01. * [`npx prized`](https://prized.dev/docs/terminal-signup) signs you up, starts a box on the free credit, and sets up this machine, in one command. * `npx prized` shows the whole sign-in link, and a link opened without its way back to the terminal says so instead of reporting it expired. * A struggling network no longer prints tunnel and forwarding errors across a `prized mosh` session. ## 0.18.0 [#0180] Released 2026-09-30. * `prized start`, the sign-up that `npx prized` runs: sign in from this terminal, get a box, and set up this machine. * The `npx prized` download retries a stalled transfer and shows its progress, instead of waiting on "Downloading…" forever. ## 0.17.0 [#0170] Released 2026-09-27. * Networks that block the tunnel reach the box through the Prized relay, and when no path works, `prized ssh`, `exec`, and `cp` stop with a hint that says what refused the connection ([Networks that block the tunnel](https://prized.dev/docs/troubleshooting#networks-that-block-the-tunnel)). * A tunnel that fell back from UDP returns to it once UDP works again. * Proxies: Macs that reach the web only through a system proxy work without `HTTPS_PROXY`, the daemon uses the proxy your shell sets, and `prized daemon proxy set` and `clear` change it; a proxy that does not answer never replaces one that works. * `prized cp` copies at the tunnel's speed, and `cp -r` moves many files at a time. * A port a program starts listening on in the box appears on your machine within seconds. * `prized ssh` and `exec` work on a computer whose clock runs behind, instead of failing with `Permission denied (publickey)`. * One-shot `prized ssh` and `exec` end without a stray "operation aborted" message. ## 0.16.0 [#0160] Released 2026-09-22. * On Windows, the first run names anything that blocks it and waits for its daemon to start. * `prized agents handoff claude` says when the box already holds a Claude sign-in that stays in use. * A folder dragged into `prized ssh` skips the files your sync ignore rules skip. * The low-disk check before a new sync measures the disk the project will land on. * Reliability fixes for [`prized mount`](https://prized.dev/docs/mount). ## 0.15.0 [#0150] Released 2026-09-21. * Text you copy in a `prized ssh` session lands on this machine's clipboard, in Apple Terminal too. * `prized ports share PORT` gives a box port a prized.dev link that members of your workspace open in a browser ([Share a port](https://prized.dev/docs/ports#share-a-port)). * The box agent recognises Ghostty, so tmux attaches from a Ghostty terminal. * A box renews its SSH host certificate on its own before it expires. ## 0.14.2 [#0142] Released 2026-09-15. * Sync pauses before staging fills your Mac's disk. ## 0.14.1 [#0141] Released 2026-09-15. * A folder dropped into `prized ssh` lands on the box all at once, never half copied. ## 0.14.0 [#0140] Released 2026-09-15. * `prized setup` and `prized agents handoff claude` copy this machine's Claude Code sign-in to the box, or, when this machine is not signed in, leave `/login` to you on the box; they no longer create a long-lived Claude token. ## 0.13.0 [#0130] Released 2026-09-14. * [`prized mount`](https://prized.dev/docs/mount) runs in the background, reconnects by itself when the box sleeps or the connection drops, and answers Finder faster. ## 0.12.4 [#0124] Released 2026-09-10. * `prized setup` offers the folder you ran it in even when it is not a git repository, and says so when nothing was synced. ## 0.12.3 [#0123] Released 2026-09-09. * After `prized box rebind` (a box recreated under its name), the next connection reaches the new box right away. ## 0.12.2 [#0122] Released 2026-09-07. * `prized setup` starts your agent on the box and leaves this terminal where it was; `--attach` hands the terminal over. * `prized cp` never follows a symbolic link and leaves hard links out of uploads. * An SSH alias stays bound to the box it first connected to, and says so when the name moves to another box ([Troubleshooting](https://prized.dev/docs/troubleshooting)). * Environment names are unique regardless of case, and the CLI resolves a name exactly or not at all. * `prized desktop` prints the VNC password only with `--no-open` or `--json`. * The installer verifies the release's signature before it runs the binary, and never hands your token to an unverified one. * The commands that hand credentials to a box refuse a restricted box, and a restricted box never receives environment secret files ([Safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties)). ## 0.12.1 [#0121] Released 2026-09-04. * Several boxes from one machine: `--box NAME` (or `PRIZED_BOX`) picks the box, each with its own daemon ([Several boxes](https://prized.dev/docs/boxes#several-boxes)). * The daemon installs from a shell outside the macOS login session, such as an SSH session into the Mac. * `prized setup` installs the environment's tools side by side, so it finishes sooner. * `prized doctor` names an SSH `Include` that SSH ignores. * A project under `~/Documents` keeps its `.env` files when it syncs. ## 0.12.0 [#0120] Released 2026-09-02. * A Windows client: install from PowerShell; the daemon runs as a per-user scheduled task ([Windows](https://prized.dev/docs/cli#windows)). --- # Security and trust > How a box is reached, what the control plane can and cannot see, where credentials and secrets go, and how to report a problem. URL: https://prized.dev/docs/security This page describes the product as built, including what it does not do yet. ## Reaching a box [#reaching-a-box] You reach a box over SSH inside an encrypted WireGuard tunnel that the CLI builds in its own process. Nothing else on the box is reachable from the internet. * **The tunnel.** `prized login` generates a WireGuard key and an SSH identity for this install and sends Prized only the public halves. No root, no TUN device, no change to your network settings. * **What the box exposes.** Two inbound ports: the tunnel, and its TCP 443 fallback for networks that block UDP. No inbound SSH port. Networks that refuse both reach the tunnel through the Prized relay (below). * **SSH inside the tunnel.** The box presents a Prized-signed host certificate (renewed automatically), so there is no trust-on-first-use prompt. Each connection authenticates with a user certificate minted for your token and box, valid 15 minutes. The box account has no password. ### Other ways in [#other-ways-in] | Path | Credential | What it reaches | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Browser terminal, files, desktop | A ticket from your signed-in dashboard session: one box, 10 minutes, never in a URL | A relay opens its own tunnel and certificate per request and terminates both encrypted hops: it can read the session as it passes, and stores nothing. | | Box monitor | A monitor ticket, same lifetime | The box's process and port list, nothing else. | | Shared port link, members | A viewer cookie on the link's own hostname, minted from your signed-in session, valid 12 hours, checked against your membership on every use | One port on one box, over HTTP, through the terminal's relay, which can read the traffic as it passes. Only workspace members who can see the box get one; stopping the share, deleting the box, or removing the member ends it ([Share a port](https://prized.dev/docs/ports#share-a-port)). | | Shared port link, anyone or public | A visitor cookie on the link's own hostname, on a separate domain that never receives a prized.dev cookie. An anyone link's secret is shown once, stored hashed, and moves from the address into the cookie when the visitor continues past the warning page, which loads nothing and sends no referrer. Valid 12 hours or until the link expires (30 days at most), checked on every use | The same one port on one box, through the same relay, and nothing else: the cookie works only through the relay, which pins the port. A visitor never wakes a box. Stopping or replacing the link, its expiry, deleting the box, or removing the member who made it ends it ([Share a port](https://prized.dev/docs/ports#share-a-port)). | | Phone (the ChatGPT app) | A phone password for one box: shown once, stored hashed, revocable | The SSH gateway checks it, wakes the box, and bridges the session, which it can read as it passes. Terminal only; repeated failed passwords are rate limited without banning other phone passwords on the same network. New connections can be refused while the gateway is busy; existing sessions are unaffected. | | The Prized relay, for networks that block the tunnel | A pass issued with your tunnel, for that box only. It ends with the tunnel: on expiry, or within about a minute of the device being revoked, the member removed, or the tunnel disconnecting | The box's tunnel port, nowhere else. It sees only encrypted WireGuard traffic: it cannot read or change your session, which stays encrypted end to end between your machine and the box. | | Desktop apps (Claude Desktop) | This machine's SSH public key, registered by `prized login` | A restricted, tunnel-only key ([Claude Desktop remote](https://prized.dev/docs/claude-desktop#what-the-app-can-do)). | | Scripts and agents | Your API key (`dcp_`) | Everything the dashboard can do, the [edge API](https://prized.dev/docs/exec#the-edge-api) included, except minting or rotating credentials or changing membership, roles, or policies, unless the key was made with less access or for some boxes only ([What a key can do](https://prized.dev/docs/workspaces#what-a-key-can-do)). An owner's key also reaches billing; a contractor's, only the boxes they own. | **Sandboxes.** Commands and files in a [sandbox](https://prized.dev/docs/sandboxes) go through Prized's edge with your API key, which needs Sandboxes: Write. The edge checks the key on every request; it never enters the sandbox. ## Identity and access [#identity-and-access] You sign in with Google or GitHub and a verified email address; a Prized account has no password of its own. A box belongs to a workspace, and its owner is the member who created it. Owners and members reach every box in the workspace; a contractor reaches only the boxes they own. Only the owner changes billing, the claimed domain, the invite link, membership and roles, policies, and the data-retention switch ([Workspaces](https://prized.dev/docs/workspaces), [Teams](https://prized.dev/docs/teams)). ### Credentials Prized issues [#credentials-prized-issues] | Credential | Scope | Stored as | Ends when | | ---------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | API key `dcp_` | The workspace (or the boxes it is limited to), for one machine or script | sha256 hash; shown once | It expires (after 7 days to a year, or never for an owner's key; a machine's after 180 days unused), you rotate or revoke it, or the member is removed; its tunnel closes and its device key leaves your boxes ([API keys](https://prized.dev/docs/workspaces#api-keys)) | | Terminal and monitor tickets | One box | sha256 hash | 10 minutes after minting | | Phone password | One box | sha256 hash; shown once | Revoked, replaced, the box deleted, or its member removed | | Device SSH key | One API key | ed25519 public key | With its key ([Remove a computer](https://prized.dev/docs/claude-desktop#remove-a-computer)) | | SSH user certificate | Your workspace and one box | Not stored; one [audit row](https://prized.dev/docs/teams#audit-log) (`box.connect`) records issuance: who, which credential, which box, when; not connection success | 15 minutes after minting | | AWS identity token | One box, for the IAM roles the workspace owner connected | Not stored; the box holds and renews it | One hour after minting; the box stops receiving new tokens when the role is removed or its owner leaves or is removed from the workspace ([AWS access](https://prized.dev/docs/aws)) | Only a signed-in dashboard session (the browser step of `prized login` is one) can mint or rotate a key, a ticket, or a phone password, so a leaked API key cannot mint more credentials. A key limited to some boxes, or to less access, cannot register a device SSH key, since boxes accept one for the whole workspace. Removing a member revokes everything they created. Once you request deletion of a box, its terminal and monitor tickets and phone password no longer reveal it in box lists. ## Credentials and secrets [#credentials-and-secrets] Your agents' sign-ins go from your machine to the box over SSH and never touch the control plane ([Credentials](https://prized.dev/docs/credentials)); no API route accepts one, and CLI telemetry carries command names only. * **Secrets and connectors.** A stored secret is a placeholder in the box's environment; the box's broker swaps in the real value on HTTPS requests to the hosts you allowlisted and refuses plain HTTP. Values are encrypted at rest and never read back. A secret with [grants](https://prized.dev/docs/teams#scoped-secrets) is decrypted only for the boxes named or owned by the members named; one without, for every box whose owner is not a contractor ([Secrets for agents](https://prized.dev/docs/credentials#secrets-for-agents)). * **What the broker does not protect against.** Anything running as your user can spend a mounted secret, and root can read anything on the box. The broker keeps values out of env files, shell history, logs, and pasted placeholders; it is not a defense against the box itself. * **Usage metadata.** Per secret and host, Prized records how often and when the value was swapped in; never a path, header, body, or value ([Teams: Usage](https://prized.dev/docs/teams#usage)). * **AWS access** never puts an access key on a box: the box trades a short-lived identity token Prized signs for temporary credentials on a role your administrator scoped and can revoke in your own account ([AWS access](https://prized.dev/docs/aws)). * **Environment secret files** are encrypted the same way and delivered over the box's authenticated channel ([Environments](https://prized.dev/docs/environments#vars-and-secret-files)). **Restricted boxes** receive no credentials through any channel ([Safe for third parties](https://prized.dev/docs/environments#safe-for-third-parties)). ## Data at rest and in flight [#data-at-rest-and-in-flight] * **Disks and snapshots** are encrypted at rest. Boxes run in `us-west-2` (Oregon), `us-west-1` (Northern California), `us-east-1` (Northern Virginia), `us-east-2` (Ohio) or `eu-central-1` (Frankfurt); a box's disk, snapshots and daily backups stay in its region unless you move the box. Account data and the control plane stay in `us-west-2` whatever the box's region; encrypted backups of account data are kept there and in `us-east-2` (Ohio). Prized also keeps a rolling week of encrypted disaster-recovery backups of every box disk, which no customer action removes early; what a delete keeps is on [Data retention](https://prized.dev/docs/data-retention#records-retained-after-deletion). * **The control-plane database** holds a box's identifiers, size, region, state, tunnel addresses, last vitals, settings, and the public half of its SSH host identity (host public key and certificate); secrets and secret files only as ciphertext. It also holds the [command history](https://prized.dev/docs/exec#command-history) of commands run with `prized exec` or the edge: the command line and up to the last 1 MiB of each output stream, kept 30 days, readable by members who can see the box, and kept as metadata only under [zero data retention](https://prized.dev/docs/data-retention#zero-data-retention). * **Saved CLI sign-in.** The CLI sends your saved token only to the service you signed in to, not to a different service selected by a config file. See [CLI sign-in](https://prized.dev/docs/cli#sign-in-and-set-up). * **In flight.** The site, the API, and the terminal relay serve HTTPS only; the CLI refuses a plain-`http` control-plane URL anywhere but loopback. * **Telemetry.** A running box or sandbox reports resource vitals, listening ports, and its top processes, never file names, file contents, terminal output, or shell history (the one output it sends is that of a detached command started with `prized exec` or the edge, to its command history) ([Privacy](https://prized.dev/docs/privacy), [Data retention](https://prized.dev/docs/data-retention#telemetry-retention-windows)). The top processes also reveal a box or sandbox that is [mining](https://prized.dev/docs/limits#not-allowed). The [activity page](https://prized.dev/docs/teams#team-activity) derives sessions from tunnel connections, never from keystrokes or screen contents. * **Error reports and logs** never contain request bodies, `Authorization` headers, tickets, keys, or user data; the CLI and the box agent have no crash reporting. ## On the box [#on-the-box] * **Processes.** The box agent runs as your login user; the secrets broker, `sshd`, and the tunnel run as root ([Machines](https://prized.dev/docs/machines#what-runs-on-the-box)). Their updates are signed and checksum-verified before they run. * **Your account** has passwordless sudo, so anything running as you can become root. `sshd` accepts Prized's short-lived certificates, registered device keys, and any key in `~/.ssh/authorized_keys`. * **What the control plane can tell a box:** an agent version to update to, a pause request, device keys, a renewed host certificate, environment variables and secret files, secret values, and the tunnel's peer list. No route carries a shell command to the box. * **What a full-access API key can do:** any command on any box in your workspace ([exec](https://prized.dev/docs/exec), files, [prompts](https://prized.dev/docs/remote-agents), a desktop), and with sudo, the whole machine. The [audit log](https://prized.dev/docs/teams#audit-log) records lifecycle, settings, API keys, snapshots, environments, secrets, membership, policies, and every certificate minted; commands, file transfers, prompts, and desktop starts are **reported** by the CLI or the box, not observed. It never contains stdout, file contents, secret values, keys, certificates, or tokens. * **Isolation.** Every box is its own virtual machine with its own kernel and encrypted disk, and no cloud credentials are attached to it. **Sandbox isolation.** Every sandbox is its own Firecracker microVM with its own kernel: commands run as an unprivileged user without sudo, nothing reaches it from the internet, and sandboxes cannot reach each other. ## What the control plane cannot see [#what-the-control-plane-cannot-see] * Your agents' sign-in tokens * Secret values, once saved * Commands typed over ssh, terminal output, file contents, and prompt bodies (the audit log holds a command line, a path, or a prompt's first line only when the CLI or the box reported it; the command history holds what `prized exec` and the edge ran and printed) * Your private keys, which never leave your machine ## Sub-processors [#sub-processors] The services that hold or carry your data, all in the United States except a box in Frankfurt: its disk, snapshots, and daily backups stay in Germany (`eu-central-1`). | Service | What for | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Amazon Web Services | Boxes, disks and snapshots in `us-west-2`, `us-west-1`, `us-east-1`, `us-east-2` and `eu-central-1`; release artifacts and the API, terminal relay, and SSH gateway in `us-west-2`; database backups in `us-west-2` and `us-east-2` | | Vercel | The site and dashboard, and aggregate page-view analytics | | Neon | The control-plane database | | Stripe | Billing, invoices, and card storage | | PostHog | Product events, session replay, and browser errors | | Sentry | Server-side error reports | | Cloudflare | DNS and transactional email | | Google, GitHub | Sign-in | | Anthropic | The environment advisor in `prized setup`, only when you use it; it receives a redacted inventory of your machine | Sandboxes run on Amazon Web Services in `us-west-2`. The [audit export](https://prized.dev/docs/teams#scheduled-export) posts your audit rows to an endpoint you configure; you hold that copy. ## Certifications and disclosure [#certifications-and-disclosure] Security at Prized is led by Hudson Griffith, co-founder and CEO. SOC 2 is in progress. To report a vulnerability, email the support address shown on https://prized.dev/docs/troubleshooting with steps to reproduce, before publishing it if you can. There is no bug bounty. --- # Privacy > What Prized records about how you use the product, and what it never records. URL: https://prized.dev/docs/privacy Usage data comes from two places, the CLI on your machine and the website and dashboard; neither reads what is inside your box. How a box is reached and what the control plane holds about it are on [Security and trust](https://prized.dev/docs/security). The [Privacy Policy](https://prized.dev/privacy) is the full statement: what Prized collects, who processes it, how long it is kept, and your rights. ## Never collected [#never-collected] Nothing from inside your box is sent anywhere for analytics: * Source code, files, and anything else on the box filesystem * Terminal output and shell history, the in-browser terminal included * Secret values and environment variable values * CLI arguments, flags, and paths The output of a command you run with `prized exec` or the edge is kept for you to read back, in its [command history](https://prized.dev/docs/exec#command-history), never for analytics. ## Website and dashboard [#website-and-dashboard] The site records page views, clicks, and these named product events: * Signing up (which sign-in provider, and where you came from) * Creating a box, the box reaching running, or waiting for capacity * Opening the browser terminal; signing in from the CLI * Using the CLI, at most once a day per workspace (the first command's name, the CLI version, and your OS), from its [usage telemetry](https://prized.dev/docs/cli#usage-telemetry) * Pausing, resuming, resizing, and deleting a box; changing auto-pause * Copying a CLI command; the onboarding steps you view * Starting checkout (with the plan amount); activating a plan; paying for a top-up (with its amount) * On a campaign page: the sections you scroll to, and how much of its film you watch Events also note whether the page is open inside a social app's built-in browser (Instagram, Facebook, Threads, TikTok, LinkedIn, Snapchat), read from the browser's user agent. Once you sign in, events are tied to your account, including the pages you viewed before signing up in the same browser, so a broken flow can be traced to the account that hit it. "Where you came from" is one first-party cookie, `prized_src`, set on the first page you land on and kept 30 days: the referring site's hostname, the landing path (invite and install tokens removed), and any campaign tags (`utm_source`, `utm_medium`, `utm_campaign`, `ref`) on the link you followed. It holds no identifier and is never rewritten by a later visit. With Do Not Track on, it is not set, a stored one is removed on your next page load, and a sign-up callback is not recorded. The New box form and the onboarding tour pick the region nearest you from your approximate location, which our host derives from your network address for each request; it is used for that page and not stored. To show round trips, your browser also sends a few empty requests to each region's AWS endpoint (`dynamodb..amazonaws.com/ping`); they carry nothing about you beyond the request itself, and the times stay in your browser. Each sign-in records its time, network address, and browser, kept 90 days to investigate abuse and account takeover ([Data retention](https://prized.dev/docs/data-retention#telemetry-retention-windows)); **Settings → Sessions** shows the address of each of yours. `prized login` registers your computer's SSH public key with each workspace it signs in to, so desktop apps can reach your boxes. If one computer signs in to several new workspaces, we keep a one-way fingerprint of that key and those workspaces' identifiers, to investigate abuse of the free credit. We keep them for 90 days after we last added a workspace to that computer's record (we add one when the pattern is detected, within minutes of the use that completes it; the uses it counts can be up to a day older) and delete them in the hourly cleanup after that, so a later addition restarts the 90 days for the whole record. A workspace held because of it keeps the same fingerprint and identifiers in its hold record for 90 days after the latest use of that computer's sign-ins the hold counted ([Data retention](https://prized.dev/docs/data-retention#telemetry-retention-windows)). Before an analytics event leaves your browser, its URL is cut to the path: query strings and fragments are dropped, and tokens in private links (invites, install links) become a placeholder. Pages also tell your browser not to send their address as a referrer when loading resources or following links. ## Session replay [#session-replay] While you are signed in, the dashboard records a replay of the interface, masked in your browser before anything is sent: * Every input and every piece of text is masked: a replay shows layout, clicks, and navigation, never the words on the page. * The terminal, the secrets panel, and the environment variables panel record as empty placeholders. Signed-out visitors are never recorded, so the marketing pages and these docs produce no replay. ## Turning it off [#turning-it-off] * **Browser.** The site respects Do Not Track: with it on, the site records nothing, no page views, events, or replay. * **CLI.** A separate switch, under [CLI: usage telemetry](https://prized.dev/docs/cli#usage-telemetry). ## How long it is kept [#how-long-it-is-kept] Retention windows for box metrics, process snapshots, and CLI usage aggregates, and how zero data retention shortens them, are on [Data retention](https://prized.dev/docs/data-retention). ## Where it goes [#where-it-goes] * **PostHog** holds the product events, the account they belong to, session replay, and error reports when a page throws, in its US cloud. * **Vercel Web Analytics**, run by the platform that hosts the site, holds aggregate page-view counts (visits per page, by country and browser). It is never told who you are, sets no cookie, and sees the same cut-down URL. An ad blocker may block the analytics requests; that affects our telemetry, never the dashboard.