# Quivvo: one computer per AI agent Quivvo is a cloud service that gives each AI agent its own Linux machine: a Firecracker microVM with root access and a persistent disk, driven from a CLI, a REST API or a hosted MCP server. Machines suspend themselves when idle and bill by the second. ```bash command -v quivvo >/dev/null || npm i -g quivvo@latest quivvo sh ``` That installs the CLI, signs you in, creates a machine and opens a shell in it. New accounts get $5 of credit, with no card needed, once per email address. If you're an AI agent, follow [the setup runbook](https://tryquivvo.com/setup.md) instead. New accounts wait in a queue for now, and the credit arrives with their turn. Each person who signs up with your invite link moves you up 10 places, and a paid plan skips it. ## What you get - **A real Linux machine.** Each one is a Firecracker microVM with its own kernel and root. The default template is Debian 13 with Docker Engine, Claude Code, Codex, the GitHub CLI, Node.js, Bun, Python, Go, and Chromium with Playwright MCP installed, and a desktop you start when you want to watch. - **Suspends when idle.** After 30 idle minutes, or 15 on the Free plan, it suspends and only pays for disk. Suspend keeps memory and processes when it can, and otherwise stops the machine, which keeps the disk. The CLI and API report which one happened. The next command wakes it. - **A disk that stays.** Everything under `/data` survives stops, restarts and resizes. - **Your choice of region.** 2 regions, in Europe and North America: `ams` (Amsterdam), `iad` (Ashburn, Virginia). A machine stays where you put it. - **Hand a task to Claude Code or Codex.** `quivvo agent prompt` starts it on the machine and returns at once. `quivvo agent result` reads what it came back with, and `--diff` what it changed. - **Background jobs.** A job keeps running when you disconnect, for up to 24 hours, and its output stays in `quivvo jobs logs`. - **Snapshots and forks.** Save a machine's disk, then start new machines from it or copy a machine for each attempt. Copies get the disk and boot fresh. - **A public URL.** Publish a port and the server inside gets an HTTPS link, public or behind a token. It serves one port at a time and answers while the machine runs. - **Built for agents.** A CLI with `--json` and meaningful exit codes, a REST API with an OpenAPI document, and a hosted MCP server for Claude Code, Cursor and Codex. An agent with no browser can sign itself up, and you approve it with a code. ## What it costs $0.005 per vCPU-hour and $0.011 per GiB-hour of memory while running, $0.20 per GiB-month of disk. A 2 vCPU, 4 GiB machine costs $0.054 an hour while it runs. See [pricing](https://tryquivvo.com/pricing.md) and [how that compares](https://tryquivvo.com/compare/sandbox-pricing.md). ## Where to go next - [Quick start](https://tryquivvo.com/docs/quickstart.md) - [Everything for agents](https://tryquivvo.com/agents.md) - [REST API](https://tryquivvo.com/docs/api.md), [OpenAPI document](https://tryquivvo.com/api/v1/openapi.json) - [MCP server](https://tryquivvo.com/docs/mcp.md) - [All docs, in one file](https://tryquivvo.com/llms-full.txt) --- # Set up Quivvo You're an AI agent setting up Quivvo for your owner. Quivvo gives you Linux machines in the cloud, with root and a persistent disk. Run these steps in order. Stop at the first step that fails and report it. Step 5 is what decides PASS. Never print an API key, and never put one in a command line. Everything below works without showing it. To check a client's config, use `claude mcp list`, not `claude mcp get`, which prints the key. `Connected` there only means the server answered, even with a wrong key, so it doesn't replace step 5. ## 1. Which client are you? Use the product you're running in: `claude` for Claude Code, `codex` for Codex, `cursor` for Cursor. If you can't tell, `which claude codex cursor` lists what's installed; ask your owner if that lists more than one. For any other client, use the CLI alone and leave out step 4. ## 2. Install the CLI It needs Node.js 22 or later. ```bash node --version command -v quivvo >/dev/null || npm i -g quivvo@latest quivvo version ``` If `npm i -g` can't write, start every `quivvo` command below with `npx quivvo@latest` instead. ## 3. Sign in ```bash quivvo whoami --json ``` Exit 0 means you're already signed in: go to step 4. Exit 3 means you aren't: ```bash quivvo login --agent --no-wait --json ``` It never blocks. It prints one JSON document with `status: "pending"`, and exits 0 on this first run. Send your owner `message_for_owner`, word for word, and **end your turn**. Don't sleep or poll: nothing can happen until they approve, and nothing exists until then. When your owner answers, or on your next turn, run the same command again: | Exit | Meaning | What to do | | --- | --- | --- | | `0` | `status: "approved"`: the key is saved | Go to step 4 | | `75` | Still pending; the document repeats the code | Remind your owner once and end your turn | | `76` | The code expired | Run it again for a new code and send that | | `77` | Your owner denied it | Stop and report FAIL at step 3 | ## 4. Connect your client New accounts wait in a queue for now. Run `quivvo whoami --json`: if `queue.waiting` is true, tell your owner `queue.position` and `queue.invite_url`, and stop. ```bash quivvo init --yes --client claude ``` Use your client from step 1. This installs the skill and writes the hosted MCP server into your client's config. The config refers to `$QUIVVO_API_KEY` rather than holding the key. MCP tools appear the next time your client starts, and only if `QUIVVO_API_KEY` is set where it starts. If the checklist `init` prints has a line for it, pass that on to your owner. The line shows how to set it, and doesn't show the key. ## 5. Verify with a real machine ```bash quivvo create --name setup-check --template minimal --size small --delete-after 30 --if-not-exists quivvo exec setup-check -- echo setup-ok quivvo rm setup-check --yes ``` Each command must exit 0, and `exec` must print `setup-ok`. This uses a little of the owner's credit, and `--delete-after` makes sure the machine goes even if you get cut off. If `create` exits 5, a plan limit is in the way. The message names the machines involved. They're your owner's, so don't touch them: report FAIL at step 5 with that message. ## 6. Report Finish with exactly one of these: ```text PASS. Quivvo is set up for : signed in to , and a machine was created, ran a command and was deleted. MCP tools appear after restarting with QUIVVO_API_KEY set. ``` ```text FAIL at step . Command: . Exit code: . Error: : . Request id: . ``` Add `--json` to the failing command to get those fields. [Errors](https://tryquivvo.com/docs/errors) says what each code means. If it looks like our bug, tell us with `quivvo feedback "what went wrong"`, leaving out anything secret. ## Next If a command might outlast your tool's timeout (often 120 seconds), run it with `--background`: when a tool timeout kills `quivvo exec`, the command is stopped too. Read the [skill](https://tryquivvo.com/skill.md) for how to use machines day to day. Everything else is listed at [https://tryquivvo.com/agents](https://tryquivvo.com/agents.md). --- --- name: quivvo description: Create and drive cloud Linux machines for AI agents with Quivvo. Use when you need a full computer away from the user's laptop to run untrusted or long-running code, use Docker, start servers with a public URL, or try several attempts in parallel. Triggers on quivvo, cloud machine, cloud VM, remote sandbox, and running something "somewhere safe". --- # Quivvo Quivvo gives you a Linux machine in the cloud: a Firecracker microVM with root and a persistent disk at `/data` (home is `/data/home`). Machines suspend when idle and wake on the next command. ## Install and sign in ```bash command -v quivvo >/dev/null || npm i -g quivvo@latest quivvo whoami --json ``` If that exits 3 you aren't signed in. This never blocks: ```bash quivvo login --agent --no-wait --json ``` Send your owner `message_for_owner` and end your turn: nothing exists until they approve. Run the same command again later. It checks once and exits 0 approved, 75 still pending, 76 expired (run it again for a new code), or 77 denied. The run that issues the code exits 0 too, so read `status`. New accounts wait in a queue for now. Run `quivvo whoami --json`: if `queue.waiting` is true, tell your owner `queue.position` and `queue.invite_url`, and stop. ## Use a machine ```bash quivvo create --name work --if-not-exists # 2 vCPU, 4 GiB, Docker, Node, Python, Go, gh, Claude Code, Codex quivvo exec work -- "cd /data/home && npm test" quivvo push ./api work # what git tracks, no .git, no .env quivvo cp work:/data/home/report.txt . quivvo port publish work 3000 # prints an HTTPS URL quivvo snapshot save work --name base # copies /data, nothing else quivvo fork work --name attempt-2 # the same; on Free, suspend work first quivvo rm work --yes # leaves its snapshots quivvo snapshot rm base --yes ``` A command gets no stdin unless you pass `--stdin`, even from a pipe: `cat patch.diff | quivvo exec work --stdin -- patch -p1`. `exec` is for text: under `--json` binary output gets changed, and `warnings` says so. Move binary data with `quivvo cp` and `quivvo push`. Add `--json` to any command: one document on stdout, errors included. With no terminal, `quivvo ssh work` opens no shell: use `exec`. ## Long work A command that waits for its result stops at 280 seconds, and if your tool's timeout (often 120 seconds) kills `exec` first, the command stops too. Start anything that might outlast either as a job. It takes up to 86400 seconds (an hour by default) and goes on if you disconnect: ```bash JOB=$(quivvo exec work --background --timeout 1800 -- "npm test") quivvo jobs wait work "$JOB" --timeout 100 # 124: still running, run it again quivvo jobs logs work "$JOB" --tail 50 ``` A job ends as `exited`, `killed` or `lost`: its process went away, usually because the machine restarted. ## One command on a throwaway machine ```bash quivvo run --push . -- "npm ci && npm test" ``` It creates a machine, runs the command, deletes the machine and exits with its code. It waits throughout, so it's for work shorter than your tool's timeout. Add `--no-wait --timeout 1800` for anything longer: it starts a job and prints the machine and the job. If `run` gets SIGKILL, the machine stays and bills until the server deletes it, at the timeout plus 30 minutes: `quivvo ls`, then `quivvo rm --yes`. ## Secrets Pass them with `--env`. Command text is kept in history, and environment variables aren't. A bare `--env KEY` forwards your local value: ```bash quivvo exec work --env GITHUB_TOKEN -- "gh repo clone acme/api" ``` ## Hand a task to Claude Code or Codex ```bash RUN=$(quivvo agent prompt work --env ANTHROPIC_API_KEY --cwd api --name fix-test -- "fix the failing test") quivvo agent wait work "$RUN" --timeout 100 # 124: still running quivvo agent result work "$RUN" --diff ``` Use `--provider codex --env CODEX_API_KEY` for Codex, or `--env CLAUDE_CODE_OAUTH_TOKEN` with a token from `claude setup-token`. Everything the agent runs can read these keys, as root, so only use code you trust with them. ## Exit codes `0` ok. `2` bad command line or request. `3` not signed in, or this key isn't allowed. `4` not found. `5` a plan limit or out of credit: do what `hint` says if you can, and otherwise pass on `owner_action`. `75` nothing changed, run it again. `124` a wait or timeout ran out. `125` `exec` itself failed, so the command's result is unknown. A remote command can exit with these too, so under `--json` look for the `error` key. ## Rules of thumb - Keep work under `/data`; it survives stops and restarts. Snapshots and forks copy only `/data`, so what `apt-get` installed isn't on the copy. - Suspend keeps memory and processes when it can, and otherwise stops the machine, which keeps only the disk. `suspend_outcome` and `resume_outcome` under `--json` say which happened. - A machine's URL serves one port, the last published, and doesn't wake a suspended machine. - `quivvo port-forward` runs until it's killed, so start it in the background. With `--json` it prints the port at once. - A running job keeps a machine awake and billing, so kill jobs you no longer need. A server started as a job ends at its `--timeout`, a day at most. - Reading files or jobs wakes a stopped machine, and its bill. `ls`, `get`, `logs` and `ps` don't. - Exit 137 with `oom_killed: true` means it ran out of memory: use a bigger size. - The Free plan runs 1 machine at a time and keeps 3. - Delete machines you're done with. `--delete-after MIN` on `create` makes that certain. ## More - Recipes: https://tryquivvo.com/docs/recipes.md - Every error code: https://tryquivvo.com/docs/errors.md - All docs in one file: https://tryquivvo.com/llms-full.txt - API: https://tryquivvo.com/api/v1/openapi.json - Sizes and prices, without a key: https://tryquivvo.com/api/v1/catalog - Something wrong? `quivvo feedback "what happened"` --- # Quivvo for agents Everything Quivvo has for AI agents, in one place. To set up a coding agent, give it this: ```text Set up Quivvo by following https://tryquivvo.com/setup.md, then report PASS or FAIL. ``` These are plain files and endpoints. We can't say which clients fetch them unprompted, so the sure way is to hand your agent the link. New accounts wait in a queue for now, and the credit arrives with their turn. Each person who signs up with your invite link moves you up 10 places, and a paid plan skips it. ## Set up an agent | Surface | What it does | Where | | --- | --- | --- | | Setup runbook | Installs the CLI, signs in without blocking, connects MCP, verifies with a real machine, reports PASS or FAIL | [`/setup.md`](https://tryquivvo.com/setup.md) | | Skill | How to use Quivvo, as a file your agent keeps. `quivvo init` installs it | [`/skill.md`](https://tryquivvo.com/skill.md), also at [`/SKILL.md`](https://tryquivvo.com/SKILL.md) | | One setup command | Shows a checklist, then installs the skill and MCP config for the tools it finds | `quivvo init` | | Agent sign-up | How an agent with no browser gets a key once its owner approves | [Agent sign-up](https://tryquivvo.com/docs/agents) | ## Drive machines | Surface | What it does | Where | | --- | --- | --- | | Hosted MCP server | 27 tools over Streamable HTTP, with OAuth sign-in or a bearer key | [`/api/mcp`](https://tryquivvo.com/docs/mcp), [server card](https://tryquivvo.com/api/mcp/server-card) | | CLI | Every command takes `--json`, and exit codes mean one thing each | [CLI reference](https://tryquivvo.com/docs/cli), `quivvo help --json` | | REST API | Everything the CLI does, over HTTPS | [Reference](https://tryquivvo.com/docs/api), [OpenAPI 3.1](https://tryquivvo.com/api/v1/openapi.json) | | SDKs | TypeScript and Python clients for the REST API, with retries and idempotency keys built in | [SDKs](https://tryquivvo.com/docs/sdks) | | What a key may do | Kind, scopes, workspace, plan limits, credit, place in the queue | `GET` [`/api/v1/whoami`](https://tryquivvo.com/docs/api#who-am-i), `quivvo whoami`, the `whoami` MCP tool | | Catalog | Templates, sizes, regions, prices and exec limits, with no key | [`/api/v1/catalog`](https://tryquivvo.com/api/v1/catalog), `quivvo catalog` | | Recipes | A one-shot command, a long job, a private repo, handing over to Claude Code or Codex | [Recipes](https://tryquivvo.com/docs/recipes) | ## Read the docs | Surface | What it is | Where | | --- | --- | --- | | llms.txt | The agent path, the facts that matter and every link | [`/llms.txt`](https://tryquivvo.com/llms.txt) | | All docs in one file | Every page, for reading at once | [`/llms-full.txt`](https://tryquivvo.com/llms-full.txt) | | Markdown of any page | Add `.md` to the URL, or send `Accept: text/markdown` | [`/docs/errors.md`](https://tryquivvo.com/docs/errors.md), for example | | Errors | Every error code, and what to do about it. API errors link here in `docs_url` | [Errors](https://tryquivvo.com/docs/errors) | ## Discovery files | File | What it says | Where | | --- | --- | --- | | AI catalog | The MCP server, skill, OpenAPI document and llms.txt | [`/.well-known/ai-catalog.json`](https://tryquivvo.com/.well-known/ai-catalog.json) | | Agent skills index | The skill, with a digest of its bytes. The format is a draft | [`/.well-known/agent-skills/index.json`](https://tryquivvo.com/.well-known/agent-skills/index.json) | | API catalog (RFC 9727) | Where the API is described | [`/.well-known/api-catalog`](https://tryquivvo.com/.well-known/api-catalog) | | MCP server card | How to connect to the MCP server | [`/api/mcp/server-card`](https://tryquivvo.com/api/mcp/server-card) | | Crawl policy | What's open to crawlers and agents | [`/robots.txt`](https://tryquivvo.com/robots.txt), [`/sitemap.xml`](https://tryquivvo.com/sitemap.xml) | Public pages point at these with `Link` headers: `rel="alternate"` for the Markdown, plus `rel="ai-catalog"`, `rel="api-catalog"` and `rel="llms-txt"`. ## Tell us what broke Agents find things people don't. Each of these reaches the team. Include `request_id` if an error gave you one, and leave out secrets. - CLI: `quivvo feedback "what happened"` - MCP: the `report_problem` tool - API: [`POST /api/v1/feedback`](https://tryquivvo.com/docs/api#feedback) --- # Quivvo docs Quivvo gives each AI agent a Linux machine of its own in the cloud. Start with one command, then read only the page you need. Every page here is also available as Markdown: add `.md` to the URL, or send `Accept: text/markdown`. ## Get started - [Quick start](https://tryquivvo.com/docs/quickstart): from nothing to a shell in your own cloud machine. - [Machines](https://tryquivvo.com/docs/machines): states, disks, sizes, templates, regions, ports and when machines get deleted. - [Recipes](https://tryquivvo.com/docs/recipes): a one-shot command, a long background job, a private repo, handing a task to Claude Code or Codex. ## Drive machines - [CLI](https://tryquivvo.com/docs/cli): every `quivvo` command, with exit codes. - [REST API](https://tryquivvo.com/docs/api): endpoints and idempotency. - [SDKs](https://tryquivvo.com/docs/sdks): TypeScript and Python clients for the API. - [MCP server](https://tryquivvo.com/docs/mcp): tools for Claude Code, Cursor and Codex. - [Agent sign-up](https://tryquivvo.com/docs/agents): how an agent with no browser gets a key. - [Errors](https://tryquivvo.com/docs/errors): every error code, and what to do about it. - [Webhooks](https://tryquivvo.com/docs/webhooks): signed events when machines change state. ## For agents [Quivvo for agents](https://tryquivvo.com/agents) lists every surface. The two to start with: - [`/setup.md`](https://tryquivvo.com/setup.md) is a runbook for an agent: it installs, signs in, connects and verifies, then reports PASS or FAIL. - [`/skill.md`](https://tryquivvo.com/skill.md) is a skill you can install. `quivvo init` does that for you. --- # Quick start You can go from nothing to a shell in your own cloud machine with two commands. It needs Node.js 22 or later. ```bash command -v quivvo >/dev/null || npm i -g quivvo@latest quivvo sh ``` That signs you in with a code in the browser, creates a machine called `dev`, and opens a shell in it. New accounts get $5 of credit, with no card needed. New accounts wait in a queue for now, and the credit arrives with their turn. Each person who signs up with your invite link moves you up 10 places, and a paid plan skips it. You can try it without installing, with `npx quivvo@latest sh`. That leaves nothing on your PATH, so every later command has to start with `npx quivvo@latest` too. If you're an AI agent, follow [the setup runbook](https://tryquivvo.com/setup.md) instead. With no terminal, `quivvo sh` opens no shell and waits for nobody: it tells you the next command to run. ## How do I give my coding agent machines? Add the hosted MCP server, and the agent creates and drives machines itself. It signs in through your browser the first time, with no key to paste: ```bash claude mcp add --transport http quivvo https://tryquivvo.com/api/mcp ``` In Cursor or VS Code it's one click: [add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=quivvo&config=eyJ1cmwiOiJodHRwczovL3RyeXF1aXZ2by5jb20vYXBpL21jcCJ9), or [install in VS Code](vscode:mcp/install?%7B%22name%22%3A%22quivvo%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Ftryquivvo.com%2Fapi%2Fmcp%22%7D). Codex and the rest are in [MCP server](https://tryquivvo.com/docs/mcp). ## How do I run commands from my laptop? ```bash quivvo exec dev -- uname -a ``` `exec` prints the command's output as it happens and exits with its exit code. A command that waits for its result stops at 280 seconds. For anything longer, or anything that might outlast a tool's own timeout, start a background job: ```bash JOB=$(quivvo exec dev --background --timeout 1800 -- "npm test") quivvo jobs wait dev "$JOB" --timeout 100 quivvo jobs logs dev "$JOB" --tail 50 ``` `jobs wait` exits 124 when the wait runs out and the job is still going, so run it again. See [recipes](https://tryquivvo.com/docs/recipes) for more. ## How do I copy files in and out? ```bash quivvo push ./app dev quivvo cp dev:/data/home/report.txt . ``` `push` sends a directory the way git sees it: tracked and new files, but nothing ignored, no `.git`, and no `.env`. `cp` copies anything either way, and sends everything you point it at. Keep work under `/data`: that's the disk that stays, and your home directory is `/data/home`. ## How do I reach a server inside the machine? ```bash quivvo port publish dev 3000 ``` That prints a public HTTPS URL for port 3000. Add `--private` for a link with a token, or use `quivvo port-forward dev 3000` for a tunnel to localhost. A machine's URL serves one port, the last one you published, and answers only while the machine runs. ## How do I stop paying for it? ```bash quivvo suspend dev ``` A suspended machine only pays for disk. Suspend keeps memory and running processes when it can, and otherwise stops the machine, which keeps the disk but not memory or processes. The command tells you which one happened. Machines also suspend themselves after 30 idle minutes by default, or 15 on the Free plan, and the next command wakes them. When you're done for good, `quivvo rm dev` deletes the machine and its disk. ## Next - Let an agent [sign itself up](https://tryquivvo.com/docs/agents). - Script everything from TypeScript or Python with the [SDKs](https://tryquivvo.com/docs/sdks), or from anything with the [REST API](https://tryquivvo.com/docs/api). --- # Machines A Quivvo machine is a Linux virtual machine in the cloud: a Firecracker microVM with its own kernel, root access, and a persistent disk at `/data`. Your home directory is `/data/home`. The default is 2 vCPUs and 4 GiB with a 10 GiB disk, in `ams` (Amsterdam), on the `universal` template, which has Docker. ## What states can a machine be in? | State | What it means | What it costs | | --- | --- | --- | | `creating`, `starting` | On its way up | Disk only, until it's running | | `running` | Working | CPU and memory per second, plus disk | | `suspending`, `stopping` | On its way down | CPU and memory per second until the provider confirms it has stopped running, plus disk | | `suspended` | Paused with memory and processes kept | Disk only | | `stopped` | Shut down. Files kept, memory and processes gone; the next start boots fresh | Disk only | | `failed` | Didn't boot, or crashed. `error` says why, and `quivvo logs` shows what's known. Restart it | Disk only | Through the CLI and REST API, commands, files, jobs, connecting and the shell all wake a suspended or stopped machine. That restarts its bill, so the CLI and the answer say so. `quivvo logs`, `quivvo status`, `quivvo ps` and `quivvo history` don't wake it, and nor does a request to its public URL. The [MCP server's](https://tryquivvo.com/docs/mcp) read tools don't either. ## What does suspend actually do? Suspend tries to keep memory and running processes. Our provider documents that up to 2 GB of memory, so it may not work on a 4 GiB machine. When it can't be done, the machine is stopped instead: the disk is kept, and memory and processes are lost. Both count as success, and the API and CLI report which one happened: - `suspend_outcome` on the machine is `suspended` or `stopped`. - `resume_outcome` on the machine is what its last boot came back as: `resumed`, with memory back, or `cold_boot`, a fresh boot from the disk. A command that woke the machine has it on its own answer too, with a warning if it was a cold boot. If your work depends on what's in memory, check those fields rather than assuming. Webhooks follow what really happened: you get `machine.stopped` for a suspend that became a stop. ## When does a machine suspend itself? A machine with no commands, shells, tunnels or running background jobs for its auto-suspend time suspends itself, with the same fallback to a stop. The default is 30 minutes. Paid plans can turn it off; the Free plan goes up to 15 minutes and uses that by default. A running background job counts as activity, so a server started as a job keeps its machine awake and billing until the job ends. A job is killed at its timeout: 3600 seconds by default, and 86400 at most. So pass `--timeout 86400` for a server, and start it again when it ends. To have a machine deleted whatever happens, create it with `--delete-after MIN` (`delete_after_minutes` in the API). `auto_delete_at` shows when. ## What is kept, and for how long? Everything under `/data` survives stops, restarts and resizes. Snapshots copy the `/data` disk, and you can start new machines from them or restore one onto a machine. Only `/data` is guaranteed. Packages installed with `apt-get` live on the root filesystem, which is meant to stay through stops and restarts. Snapshots and forks don't copy it, and a copy doesn't run the original's setup script. Restoring a snapshot, or a move to another host, can give the machine a fresh root filesystem from its template, and a setup script doesn't run again then either. So keep what you can't lose on `/data`, and be ready to install the rest again. See [setting a machine up once](https://tryquivvo.com/docs/recipes#set-a-machine-up-once). Command history keeps a machine's newest 100 commands for 30 days, and goes when the machine is deleted. A workspace keeps the newest 8 MiB of history across its machines, so very long output pushes out older commands sooner. When a machine is deleted, its setup script and ports go at once. A record of it stays for 35 days, so that repeating a delete succeeds and this month's usage can name it, and a workspace keeps the newest 500 of those. A workspace keeps its newest 1 MiB of [feedback](https://tryquivvo.com/docs/api#feedback), for 90 days. The disk lives on one host, and there are no automatic backups. Keep anything you can't lose somewhere else as well, such as a git remote. ## How do snapshots and forks work? `quivvo snapshot save` copies the disk. `quivvo fork` copies a machine's disk into new machines. Copies boot fresh with no running processes, whatever state the source was in. Each fork stands alone, and a partial failure lists the ones that couldn't be made in `failed`. Copies are made by the provider. If it won't make one, the answer is a [`provider_error`](https://tryquivvo.com/docs/errors#provider_error), nothing is created and the source is untouched. A snapshot is charged as disk, $0.20 per GiB-month for the size of the disk it copied, from when it's ready until it's deleted. A copy can still be reading from what it was copied from after it boots. So a machine can't be deleted while a snapshot or fork of it is being made, and a snapshot can't while a machine is being made from it. That's a `conflict` with `retry_after_seconds`: try again later. `quivvo rm` leaves a machine's snapshots: delete those with `quivvo snapshot rm`. A fork counts as a running machine, so on the Free plan, where 1 runs at a time, suspend or stop the source first and make one copy at a time. ## What sizes, templates and regions are there? | Size | vCPUs | Memory | Per running hour | | --- | --- | --- | --- | | `small` | 1 | 2 GiB | $0.027 | | `medium` (default) | 2 | 4 GiB | $0.054 | | `large` | 4 | 8 GiB | $0.108 | | `xlarge` | 8 | 16 GiB | $0.216 | vCPUs are shared: a machine bursts to full cores, then settles to a baseline if it stays busy for long stretches. Memory and disk are yours alone. | Template | What's on it | | --- | --- | | `universal` (default) | Debian 13 with Claude Code, Codex, Docker, the GitHub CLI, Node.js 22, Bun, Python 3.13, Go, build tools, Chromium with Playwright MCP, and a desktop on demand. | | `minimal` | Debian 13 with git, curl and Python 3.13. The smallest image. | The `universal` template also has Chromium, with Playwright MCP to let an agent drive it, and a desktop that runs only when you start it: see [letting an agent use a browser](https://tryquivvo.com/docs/recipes#let-an-agent-use-a-browser) and [watching on a desktop](https://tryquivvo.com/docs/recipes#watch-it-on-a-desktop). Anything else, you can install: you're root, so run `apt-get update` and then `apt-get install`. The `minimal` template has no browser or desktop. Regions: `ams` (Amsterdam), `iad` (Ashburn, Virginia). With no `--region`, a new machine goes to `ams`, or to the other region if that has no room, and `region` on the machine says where. Naming a region means that region and no other. A machine's disk lives on one host. If that host is full when a stopped or suspended machine starts, Quivvo tries to move it to another host in its region, with a copy of `/data` and the same URL. It comes back as a cold boot, and anything installed outside `/data` may be gone. If there's no room anywhere in the region, the start fails with [`capacity`](https://tryquivvo.com/docs/errors#capacity) and nothing changes. ## What are the Free plan's limits? New workspaces get $5 of credit, with no card needed, once per email address. On Free, 1 machine runs at a time and a workspace keeps 3, each up to 2 vCPUs, 4 GiB and 20 GiB of disk, with 3 snapshots, and creates up to 60 machines an hour. See [pricing](https://tryquivvo.com/pricing) for the other plans, or run `quivvo whoami` to see your own limits. New accounts wait in a queue for now, and the credit arrives with their turn. Each person who signs up with your invite link moves you up 10 places, and a paid plan skips it. ## How long can a command run? A command that waits for its result stops at 280 seconds, which is also its default, and exits 124. A background job takes up to 86400 seconds, 3600 by default, and goes on if you disconnect. See [recipes](https://tryquivvo.com/docs/recipes#run-something-long-in-the-background). If the caller of a waiting command goes away, the command gets SIGTERM, then SIGKILL 3 seconds later. A job isn't affected. An agent's tool timeout (often 120 seconds) is a caller going away, so use `--background` for anything that might outlast it. `quivvo exec` prints output as it's produced, all of it, so what ran before a kill is already on your side. Under `--json` without `--stream`, or with `--tail-bytes`, nothing prints until the end, so get it back with `quivvo history` and then `quivvo output`. Under `--json` and through the API, long output keeps its first 256 KiB and last 768 KiB per stream, with a line saying how many bytes were left out, and `--tail-bytes` keeps just the end. `quivvo output` shows the first 16 KiB and last 48 KiB of each stream of a past command, including what a stopped command had printed by then. It can only keep what the command's answer kept, so after `--tail-bytes` that's just the tail. A job's output is in `quivvo jobs logs`. ## What does exit 137 mean? It was killed, usually for running out of memory. When the kernel's out-of-memory killer fired while a command ran, `oom_killed` is true on the result, the job and the history entry. That's machine-wide, so on its own it doesn't prove this command was the victim, but together with 137 it usually does. Use a bigger size: stop the machine, then `quivvo resize work --vcpus 2 --mem-mib 4096`. ## How do public URLs work? `quivvo port publish` serves a port on the machine's URL. - A machine has one URL, and it serves one port: the last one published. `replaced_port` says which one it stopped serving. - A machine can have 10 published ports. `quivvo port ls` lists them all, including ones its URL no longer serves, until you unpublish them, and `default` marks the one it does. Unpublish that one and the URL serves the highest port left. - It answers only while the machine runs. A request to it doesn't wake a suspended machine, so resume it first. To keep a server up, start it as a background job with `--timeout 86400`, which counts as activity but ends at that timeout, or on a paid plan turn auto-suspend off with `quivvo auto-suspend dev 0`. - Use `quivvo port-forward` for a private tunnel to any port. It runs until it's killed, so in a tool call start it in the background. With `--json` it prints the port at once. ## How do I run Claude Code or Codex inside a machine? The `universal` template has both installed, not signed in. See [handing a task to Claude Code or Codex](https://tryquivvo.com/docs/recipes#hand-a-task-to-claude-code-or-codex). Sign-ins are saved in `~/.claude` and `~/.codex`, on the persistent disk, so they survive stops and restarts. Snapshots include them, so any machine started from a snapshot is signed in as well. Snapshots are private to your workspace. ## When are machines and snapshots deleted? Abandoned machines and snapshots are deleted, always after an email warning and a final reminder 2 days before. - **Credit at zero:** everything goes 7 days after the warning, or 30 days if the workspace has ever paid. - **On the welcome credit alone:** a machine not used for 14 days goes 7 days after the warning, and so do snapshots once no machine in the workspace has been used for that long. Adding credit, resuming the machine, or starting a machine from the snapshot cancels it. Disks and snapshots are charged until then, so a balance can dip below zero. Active paid plans are exempt, and the `delete_after` field on machines and snapshots shows the date. --- # Recipes Each of these is meant to be copied. They work from a script or an agent's tool call, with no terminal. ## Run one command on a throwaway machine ```bash quivvo run --push . -- "npm ci && npm test" ``` `run` creates a machine, sends the current directory, runs the command there, deletes the machine and exits with the command's code. It waits for the whole command, so it's for work shorter than your own tool's timeout (often 120 seconds). For anything longer, have it start a job and answer right away: ```bash quivvo run --push . --no-wait --timeout 1800 --json -- "npm ci && npm test" ``` That prints `machine`, `job` and the commands that come next: `quivvo jobs wait`, `quivvo jobs logs` and `quivvo rm`. The server has also been told to delete the machine, at the timeout plus 30 minutes. So if `run` is killed with no chance to clean up (SIGKILL), the machine bills until then at most, and on the Free plan it holds the one running slot. Any other signal, as most tool timeouts send, deletes it on the way out. To clean up, find it and delete it: ```bash quivvo ls quivvo jobs ls run-58658f99 quivvo jobs wait run-58658f99 j_8x0qvxi3k9 --timeout 100 quivvo rm run-58658f99 --yes ``` - Up to 280 seconds, it behaves like `exec`, with stdout and stderr apart. A longer `--timeout` makes it a job, up to 86400 seconds, and their output arrives interleaved. - `--push` sends what `quivvo push` would: what git tracks, no `.git`, no `.env`. - `--keep` leaves the machine there. ## Run something long in the background A command that waits for its result stops at 280 seconds, and when a tool timeout (often 120 seconds) kills `quivvo exec`, the command is stopped too. Start anything that might outlast either as a job. It takes up to 86400 seconds, 3600 by default, and goes on if you disconnect. ```bash quivvo create --name work --if-not-exists JOB=$(quivvo exec work --background --timeout 1800 -- "npm test") quivvo jobs wait work "$JOB" --timeout 100 ``` `jobs wait` exits with the job's code. It exits 124 only when the wait ran out and the job is still running, so run it again. A job that hit its own timeout exits 1 here, and `exit_code` under `--json` holds the real 124. That means a retry loop can't spin forever. ```bash quivvo jobs logs work "$JOB" --tail 50 # the last 50 lines quivvo jobs logs work "$JOB" -f # follow until it ends, then exit with its code quivvo jobs kill work "$JOB" # TERM by default quivvo jobs ls work ``` A job ends as one of: | State | Meaning | | --- | --- | | `exited` | It finished. `exit_code` is its code | | `killed` | It was signalled. `exit_code` 124 means it hit its timeout | | `lost` | Its process went away with its exit status unknown, usually because the machine restarted or stopped. Its log is still there | A running job keeps the machine awake and billing, so kill the ones you don't need. To keep a server up for as long as a job can run, a day, pass that as its timeout, and start it again when it ends: ```bash quivvo exec work --background --timeout 86400 -- "npm run dev" ``` ## Get a private repo onto a machine Send your working copy. It's what git tracks, without `.git` or `.env`: ```bash quivvo push ./api work ``` Add `--include .git` if you need to run git on the machine. To clone instead, forward a token. `--env` with no value sends your local one, and it stays out of history, where the command text would not: ```bash quivvo exec work --env GITHUB_TOKEN -- "gh repo clone acme/api" ``` `gh` is on the `universal` template. ## Hand a task to Claude Code or Codex The `universal` template has both installed, not signed in. `quivvo agent prompt` starts one as a run that outlives your command. It checks first that the agent has credentials, and exits 3 with the fix if not. A bare `--env KEY` whose variable isn't set on your side exits 2 before that, saying so. ### Claude Code Pass one of these: - `ANTHROPIC_API_KEY`, an API key. - `CLAUDE_CODE_OAUTH_TOKEN`, a long-lived token from your Claude login. Make one with `claude setup-token` wherever you have a browser, and copy it from the output: it isn't saved anywhere. ```bash RUN=$(quivvo agent prompt work --env CLAUDE_CODE_OAUTH_TOKEN --cwd api --name fix-test -- "fix the failing test") quivvo agent wait work "$RUN" --timeout 100 quivvo agent result work "$RUN" --diff ``` Or sign in once on the machine with a person at the keyboard: `quivvo ssh work -- claude`. The sign-in stays on its disk. ### Codex Pass `CODEX_API_KEY` (`OPENAI_API_KEY` works too), or sign in once on the machine with `quivvo ssh work -- codex login --device-auth`. ```bash RUN=$(quivvo agent prompt work --provider codex --env CODEX_API_KEY --cwd api --name fix-test -- "fix the failing test") quivvo agent wait work "$RUN" --timeout 100 quivvo agent result work "$RUN" ``` ### What to know - `agent wait` exits 0 when it's done, 1 if the agent failed, and 124 if it's still running. Run it again on 124. - `--name` makes a retry safe: repeating it returns the same run instead of starting a second one. - `--continue RUN` carries on from an earlier run's session. `--budget USD` stops Claude Code at a spending limit. `--timeout SEC` is an hour by default. - `agent result --json` gives `state`, `exit_code`, `session_id`, `text`, `cost_usd`, `changed_files` and `diff_stat`. - The prompt goes in a file on the machine, in `~/.quivvo/runs/`, and stays out of command history. - A running agent keeps the machine awake, and billing. Everything the agent runs can read its key, as root. It runs with permission prompts off, and `claude -p` runs a repo's hooks and `.mcp.json` with no trust prompt, so only use code you trust with those keys. ## Let an agent use a browser The `universal` template has Chromium and [Playwright MCP](https://github.com/microsoft/playwright-mcp), already set up to use it. Add the MCP server to an agent once. It's saved in the agent's settings, on the machine's disk: ```bash quivvo exec work -- claude mcp add -s user playwright -- playwright-mcp quivvo exec work -- codex mcp add playwright -- playwright-mcp ``` Then give it a task that needs a browser: ```bash RUN=$(quivvo agent prompt work --env CLAUDE_CODE_OAUTH_TOKEN --cwd app -- "start the dev server, open it in the browser and check that signing up works") ``` - The browser is headless unless the machine's desktop is running, and then you can watch it there: see [watching on a desktop](#watch-it-on-a-desktop). Either way, the agent can save screenshots to a path for you to copy back: `quivvo cp work:/data/home/app/signup.png .` - The browser keeps its profile, with cookies and sign-ins, under `~/.cache` on the persistent disk. - `chromium` is on the PATH for other tools, such as Puppeteer, or `chromium --headless --screenshot=page.png https://example.com`. It runs without Chromium's sandbox, which won't start as root. - A Playwright script of another version needs that version's Chromium: run `npx playwright install chromium`, or `playwright install chromium` for Python. It's installed outside `/data`, so a snapshot or fork doesn't copy it. - Chromium needs memory. If a page crashes or a command exits 137, use a bigger size. ## Watch it on a desktop The `universal` template has a desktop that runs only when you start it: a 1280x800 screen with a window manager, a terminal and Chromium on its right-click menu. ```bash quivvo desktop work ``` That starts it and opens the machine's Desktop tab in the dashboard, where you can watch and take over with the mouse and keyboard. Opening the tab starts it too. With a VNC client instead, forward its port and connect to `localhost:5901`: ```bash quivvo port-forward work 5901 ``` - While it runs, a browser the agent opens through Playwright MCP appears on it, and so does any window a program on the machine opens. An agent started before the desktop keeps a headless browser until its next run. - `xdotool` is there for an agent that drives the desktop itself, with `DISPLAY=:1`. - An open view keeps the machine awake, and billing, like an open shell. The desktop itself doesn't: with nobody watching and nothing running, the machine suspends as usual. - `quivvo desktop work --stop` stops it and closes its windows. A stop of the machine stops it too. It starts again the next time you open it. - Only the machine's own tunnel reaches it, with the machine's token, so VNC asks for no password. ## Set a machine up once ```bash quivvo create --name base --setup-file ./setup.sh --wait-setup quivvo snapshot save base --name base-image quivvo stop base quivvo create --name work --snapshot base-image ``` A snapshot copies `/data` and nothing else. What your script leaves there, such as a cloned repo, `node_modules` or downloads, is on the new machine. What it installs anywhere else, such as a package from `apt-get`, stays on `base`, and nothing tells you it's missing until you need it. So keep the slow work under `/data`, and give the new machine its own script for the rest. It runs once the machine is up: ```bash quivvo create --name work --snapshot base-image --setup-file ./install-packages.sh --wait-setup ``` The script is off the disk before its first line runs, so a snapshot doesn't copy it. Its output stays in the job's log, so don't print secrets. `--wait-setup` waits for the script and fails if it fails. `setup` on the machine shows `state`, `exit_code` and `job_id`, and the job's logs are the script's output. A failed script leaves the machine there and running, and billing, so you can see what went wrong. Delete it with `quivvo rm` before you try again: a setup script runs once, so `--if-not-exists` finds the same machine and doesn't run it again. The `stop` is for the Free plan, which runs one machine at a time. Copies are made by the provider. If it won't make one, the answer is an error, nothing is created and the source is untouched: see [`provider_error`](https://tryquivvo.com/docs/errors#provider_error). ## Tell a coding agent about Quivvo `quivvo init` installs the skill for the tools it finds. For a repo whose agents should all know, add this to its `AGENTS.md` or `CLAUDE.md`: ```markdown ## Cloud machines Run anything long, untrusted or heavy on a Quivvo machine, not on this computer. - Start: `quivvo create --name work --if-not-exists`. Run: `quivvo exec work -- "..."`. Finish: `quivvo rm work --yes`. - Anything that might outlast a tool call: `quivvo exec work --background --timeout 1800 -- "..."`, then `quivvo jobs wait`. - More: https://tryquivvo.com/skill.md ``` --- # Quivvo CLI `quivvo` is the command-line client for Quivvo machines. It needs Node.js 22 or later and has no dependencies. ```bash command -v quivvo >/dev/null || npm i -g quivvo@latest ``` `npx quivvo@latest` works without installing, but leaves nothing on your PATH, so every later command has to start with it too. Credentials live in `~/.config/quivvo/credentials.json` (mode 0600). `QUIVVO_API_KEY` and `QUIVVO_API_URL` override them. The default API URL is `https://tryquivvo.com`. The CLI talks to that and, for `ssh`, `cp`, `push` and `port-forward`, directly to the machine at `*.fly.dev`, so a restricted network has to allow both. ## How do scripts and agents use it? - **`--json`** on any command prints one JSON document on stdout, errors included, and nothing on stderr. Warnings go inside the document. Following a stream (`exec --stream`, `jobs logs -f`) prints one JSON document per line instead. - **Errors under `--json`** have `code`, `message`, `retryable`, `retry_after_seconds`, `status`, `exit_code`, `hint`, `docs_url`, `request_id` and `owner_action`. [Errors](https://tryquivvo.com/docs/errors) lists the codes. If `hint` names a step you may take, take it first, such as suspending a machine to make room. `owner_action` is what only the workspace's owner can do, so pass that on when nothing else works. - **Remote exit codes pass through.** `exec`, `run`, `ssh`, `jobs wait` and `jobs logs -f` exit with the remote command's code, which can be 2, 3 or 75 too. Under `--json`, look for the `error` key rather than going by the code alone. The one difference: `jobs wait` keeps 124 for "still running", so a job that hit its own timeout exits 1 there and 124 from `jobs logs -f`. - **No terminal.** `sh` opens no shell and waits for nobody: with no key it exits 3 and names `quivvo login --agent --no-wait --json`, and with a key it gets the machine running and prints the next `exec`. `quivvo sh dev -- uname -a` runs that command and exits with its code. `ssh` does the same, on a machine that exists. - **Tool timeouts.** `exec` prints output as it's produced, so what ran before a kill is already on stdout, but the kill stops the command. Under `--json` without `--stream`, or with `--tail-bytes`, nothing prints until the end, so get it back with `quivvo history` and then `quivvo output`. If it might outlast your tool's timeout (often 120 seconds), use `--background`. `run` waits for the whole command too: `run --no-wait` starts a job instead. - **Safe retries.** A `create` that fails leaves nothing behind, with one exception: when `--wait-setup` fails, the machine is there, running and billing, so you can read the script's log. Delete it with `quivvo rm`. `--idempotency-key` gets the first answer back within 24 hours, and a different request with the same key is a `conflict`. `--if-not-exists` uses the machine if it's already there, and `created` under `--json` says which. It won't run a setup script a second time. Repeating `rm` or `snapshot rm` succeeds. `fork` lists partial failures in `failed` and exits with the first failure's code, so a 5 doesn't mean nothing was made: `machines` lists the copies it did make, which are running and billing, and `hint` says how to delete them. - **Limits.** A command that waits for its result stops at 280 seconds, which is also its default. With `--background`, the default is 3600 and the most is 86400. See [recipes](https://tryquivvo.com/docs/recipes). - **The queue.** `quivvo queue` prints your place, how many have joined from your link, and the link. While a workspace is waiting, `whoami` shows the same, and whatever creates a machine exits 5 with the code `queued`. Pass the link to the owner and stop: retrying won't help. - **Keys.** `quivvo login` gets a key that drives machines. `keys` and `claim` also need `keys:write`, which you tick when you approve the sign-in. It's off by default because anyone can start a sign-in and send you the link. A key with only `machines:read` can `cp` a single file off a running machine and nothing else of what opens a connection: copying a directory or uploading, `push`, `ssh` and `port-forward` need `machines:write`. - **Secrets.** A bare `--env KEY` forwards your local value, and it stays out of history. The command text doesn't. - **stdin.** A command gets none unless you pass `--stdin`, even from a pipe, because a stdin that never closes would hang it. When a pipe or file was given without it, the CLI warns that it was ignored. `--stdin` takes up to 1 MB. - **Binary data.** `exec` is for text. Its plain stdout and `--stdin` carry binary bytes unchanged, but `--json` and `--tail-bytes` can't, and say so in `warnings`. Use `cp` and `push` for files. - **`push` and `cp` differ.** `push` sends what git tracks and new files, with no `.git` and none of what usually holds a credential, tracked or not: `.env` and its variants such as `.env.*` and `prod.env`, `.ssh`, `.aws`, `*.pem`, `.netrc`, `*.tfvars`, `.mcp.json`, cloud service-account files, and the logins in `.claude` and `.codex`. That's a list of names, so it can't catch everything. It says what it left out, and `--include` sends it. `cp` sends everything you point it at, and copying into an existing directory nests, like `cp`. Bringing a directory back from a machine leaves out `.git/hooks`, because an agent had root there and the next `git commit` on your computer would run them. `.git/config`, `.envrc` and `.vscode/tasks.json` can run commands too, so read what you bring back before you work in it. ## Exit codes | Code | Meaning | | --- | --- | | `0` | ok | | `1` | something else went wrong | | `2` | the command line or a request was wrong | | `3` | not signed in, or this key isn't allowed to | | `4` | no such machine, job, snapshot or file | | `5` | a plan limit, or out of credit: the error says who can fix it | | `75` | nothing changed and it may work later: run it again | | `76` | `login`: the code expired; run it again for a new one | | `77` | `login`: the workspace's owner denied it | | `124` | a wait or the command's timeout ran out | | `125` | `exec` or `run` itself failed, so the command's result is unknown | | otherwise | `exec`, `run`, `ssh`, `jobs wait` and `jobs logs -f` exit with the remote code | ## Environment | Variable | Meaning | | --- | --- | | `QUIVVO_API_KEY` | Use this key instead of the saved credentials | | `QUIVVO_API_URL` | API to talk to (default https://tryquivvo.com) | | `QUIVVO_DEBUG` | Log every request to stderr | | `NO_COLOR` | Plain output | ## Commands `` is a machine's name or id. Every command also takes `--json` and `--help`. Run `quivvo --help` for what each flag does, or `quivvo help --json` for all of it as data. ```text quivvo sh [name] [--template T] [--size S] [--region R] [-- cmd...] Sign in if needed, create or resume a machine (default "dev"), open a shell quivvo init [--client claude|codex|cursor ...] [--yes] [--dry-run] [--project] [--inline-key] [--no-skill] [--no-mcp] Set up the skill and the MCP server for the AI tools on this computer (shows a checklist first) quivvo login [--agent] [--name N] [--scope S ...] [--no-wait] [--no-browser] Sign in with a device code (opens the browser unless --agent) quivvo logout [--revoke] Remove the saved credentials quivvo whoami Show the workspace, plan limits, and what this key may do quivvo queue Show your place in the queue and your invite link quivvo create [--name N] [--template T] [--size S | --vcpus N --mem-mib M] [--disk G] [--region R] [--snapshot S] [--setup-file PATH] [--env K[=V] ...] [--auto-suspend MIN] [--delete-after MIN] [--if-not-exists] [--idempotency-key KEY] [--wait-setup] [--no-wait] Create a machine (waits until it's running) quivvo ls [--all] [--state S] List machines quivvo get Show a machine (with its daemon's status when it's running) quivvo exec [--stream] [--background] [--cwd D] [--env K[=V] ...] [--timeout SEC] [--tail-bytes N] [--stdin] [--] Run a command and print its output; exits with the command's exit code quivvo run [--push DIR] [--timeout SEC] [--keep] [--no-wait] [--cwd D] [--env K[=V] ...] [--template T] [--size S] [--region R] [--name N] [--] also: --include PATTERN, --exclude PATTERN One command on a throwaway machine: create, optionally push, run, delete quivvo ssh [--print] [--cwd D] [-- cmd...] Interactive shell on the machine; --print shows an OpenSSH command quivvo ssh-proxy [--port N] Pipe stdin/stdout to the machine's port 22 (an OpenSSH ProxyCommand) quivvo cp [--exclude PATTERN ...] Copy files or directories to or from a machine (`m:/path` marks the machine side) quivvo push [:path] [--exclude PATTERN ...] [--include PATTERN ...] Upload a directory, honouring .gitignore (default: ~/ on the machine) quivvo jobs ls List a machine's jobs, newest first quivvo jobs logs [-f] [--tail N] [--offset BYTES] Print a job's output (stdout and stderr interleaved) quivvo jobs wait [--timeout SEC] Wait for a job to end; exits with its exit code quivvo jobs kill [--signal TERM|KILL|INT] Signal a job's process group (default TERM) quivvo ps List processes on a machine quivvo history [--limit N] Commands run with exec, newest first (works while stopped) quivvo output Print a past exec's output (the first 16 KiB and last 48 KiB of each stream) quivvo logs [--lines N] Boot and daemon log; of a machine that isn't running, its state, error and the provider's events quivvo status The machine daemon's status: uptime, idle time, sessions, memory, disk quivvo suspend [--no-wait] Suspend a machine (keeps memory and processes when it can; otherwise it stops) quivvo resume [--no-wait] Resume a suspended machine quivvo stop [--no-wait] Stop a machine (the disk is kept) quivvo start [--no-wait] Start a stopped machine quivvo restart [--no-wait] Power-cycle a machine quivvo rm [--yes] Delete a machine and its disk (asks first) quivvo snapshot save --name N [--no-wait] Save a machine's disk as a snapshot quivvo snapshot ls List snapshots quivvo snapshot rm [--yes] Delete a snapshot (by name or id) quivvo restore --snapshot N [--yes] [--no-wait] Replace a machine's disk with a snapshot's (same id, name and URL). What's on /data now is lost, and anything installed outside /data may be too quivvo fork [--count N] [--name N] [--no-wait] Copy a machine's disk into new ones quivvo port publish [--private] Serve a port on the machine's URL quivvo port unpublish Stop serving a port quivvo port ls List published ports quivvo port-forward [:] Forward a local port to a port inside the machine (private, no publishing) quivvo desktop [--stop] [--no-browser] Start a machine's desktop and open it in the dashboard (--stop stops it) quivvo auto-suspend Suspend after this many idle minutes (0 turns it off) quivvo resize [--vcpus N --mem-mib M] [--disk G] Change CPU and memory (while stopped) or grow the disk (any time) quivvo usage This period's cost, credit balance and per-machine usage quivvo catalog Templates, sizes and regions you can create machines with quivvo keys ls List API keys quivvo keys create --name N [--scope S ...] Create an API key (the secret is printed once) quivvo keys rm Revoke an API key quivvo feedback [--machine M] [--context TEXT] Tell us something went wrong or could be better quivvo claim [--yes] [--deny] [--scope S ...] Approve (or --deny) a sign-in code shown by `quivvo login --agent` quivvo mcp init claude|cursor|codex [--project] [--inline-key] Write the hosted MCP server into an AI tool's config, referring to $QUIVVO_API_KEY quivvo mcp start A stdio MCP server that forwards to the hosted one quivvo agent prompt [--provider claude|codex] [--cwd D] [--env K[=V] ...] [--model M] [--continue RUN] [--name KEY] [--timeout SEC] [--budget USD] [--] Start a run; prints its id quivvo agent wait [--timeout SEC] Wait for a run: 0 done, 1 the agent failed, 124 still running quivvo agent result [--diff] Print what a run came back with; exits like `agent wait` quivvo version Print the CLI version ``` --- # Quivvo REST API The Quivvo REST API creates and drives machines over HTTPS. The base URL is `https://tryquivvo.com/api/v1`, it takes and returns JSON, and it's described by an [OpenAPI 3.1 document](https://tryquivvo.com/api/v1/openapi.json). A field a route doesn't take is ignored, not refused, so check names against that document. The MCP server refuses them. ## Auth `Authorization: Bearer qvk_...` or `X-API-Key: qvk_...`. A key belongs to one workspace and carries scopes: | Scope | Allows | | --- | --- | | `machines:read` | list and read machines, history, jobs, files (read), ps, ports, usage | | `machines:write` | create, exec, files (write), lifecycle, ports, connect, snapshot save, fork, restore | | `machines:delete` | delete machines and snapshots, and set or bring forward `delete_after_minutes` on a machine that already exists | | `keys:write` | create and revoke API keys, approve device and agent sign-ins. A key can't create a key with scopes it doesn't have itself | A read-only key can read every ordinary file on a machine, so whatever anyone saved there, and job logs. It can't read `/proc`, `/sys`, `/dev`, `/run` or the daemon's own state, even through a link, and gets `forbidden`: reading can't turn into running. A key with `machines:write` can run anything as root, so it has no such limit. `POST /machines/:id/connect` needs `machines:write`, so a read-only key reads through `GET /machines/:id/files`. Keys made by `quivvo login` (kind `cli`) get `machines:read`, `machines:write` and `machines:delete`, plus `keys:write` if the person approving ticks it. Keys an agent gets from `login --agent` (kind `agent`) get the scopes the owner approved, never `keys:write`. Routes marked **session** also accept the dashboard's signed-in cookie. With that cookie, a request that changes something has to come from this site's own pages, or it's `forbidden`. Billing routes are session-only. Only a key with `machines:write` wakes a machine. Reading files or jobs with a read-only key on a machine that isn't running answers `machine_not_running`, because waking one restarts its bill. ## Who am I? `GET /whoami` needs a key with any scope, and shows what it may do: `kind`, `key`, `email`, `scopes`, `workspace` (`id`, `name`, `plan`, `plan_status`, `credit_balance`), `limits`, `exec`, `machines` (`total`, `running`) and `queue`. ## The queue `GET /queue` needs a key with any scope, and shows your own workspace's place, the same object as `queue` in `whoami`: ```json { "waiting": true, "position": 412, "invites": 3, "moves_per_invite": 10, "max_counted_invites": 50, "invite_url": "https://tryquivvo.com/login?invite=abcd1234" } ``` Or `{ "waiting": false }` once it's let in. While it's waiting, a workspace gets [`queued`](https://tryquivvo.com/docs/errors#queued) from anything that creates a machine. Each new mailbox that signs up with `invite_url` and confirms its email moves it up `moves_per_invite` places, for up to `max_counted_invites` of them. Upgrading or topping up lets it in. `queue_open` in the catalog says whether a new sign-up waits. ## Errors ```json { "error": { "code": "not_found", "message": "No machine named dev.", "retryable": false, "retry_after_seconds": null, "docs_url": "https://tryquivvo.com/docs/errors#not_found", "request_id": "req_..." } } ``` Branch on `error.code`. When `retryable` is true, retry after `retry_after_seconds`. Every response carries an `x-request-id` header, and `request_id` repeats it. `owner_action`, when present, is `{ url, message }`: something only the workspace's owner can do, signed in on the dashboard. [Errors](https://tryquivvo.com/docs/errors) lists every code and what to do about it. A path that doesn't exist is a `not_found` like any other. A path that exists, asked with a method it doesn't take, answers 405 with no body. ## Idempotency Lifecycle `POST`s (create, start, stop, suspend, resume, restart, snapshot, fork, restore) accept `Idempotency-Key: `. Repeating a request with the same key within 24 hours returns the first response instead of acting again. A workspace keeps its newest 1,000 keys, so a retry of an older one acts again. `POST /machines` also takes it as `idempotency_key` in the body. `DELETE` on a machine or snapshot that's already gone succeeds with `already_deleted: true`, so retrying is safe. What's deleted is remembered for 35 days, and a workspace's newest 500 deleted machines. After that it's `not_found`. ## Objects ```json Machine { "id": "m_8f3k2l9x", "name": "dev", "state": "creating"|"starting"|"running"|"suspending"|"suspended"|"stopping"|"stopped"|"failed"|"deleted", "template": "universal", "region": "ams", "vcpus": 2, "memory_mib": 4096, "disk_gib": 10, "url": "https://...", // public URL for the last port published, null until one is "auto_suspend_minutes": 30, // 0 = never "created_at": "...", "started_at": "..."|null, "last_activity_at": "..."|null, "error": null|"why it failed", "hourly_cost": 0.054, "delete_after": null|"...", // set once the owner was warned that this abandoned machine will be deleted "auto_delete_at": null|"...", // when the server deletes it, from delete_after_minutes "setup": null|{ "state": "pending"|"running"|"succeeded"|"failed", "exit_code": null|0, "job_id": null|"j_..." }, "suspend_outcome": null|"suspended"|"stopped", // what the last suspend turned into; cleared on the next boot "resume_outcome": null|"resumed"|"cold_boot", // what the last boot came back as; cleared when it next goes down "warnings": ["..."] // only when something went differently from what you asked } Snapshot { "id": "snap_...", "name": "base", "machine_id": "m_..."|null, "template": "universal", "disk_gib": 10, "state": "creating"|"ready"|"failed", "created_at": "...", "delete_after": null|"..." } Job { "id": "j_...", "cmd": "...", "argv": null, "cwd": "...", "pid": 1, "state": "running"|"exited"|"killed"|"lost", "exit_code": null|0, "started_at": "...", "finished_at": null|"...", "timeout_at": "...", "oom_killed": false } Exec { "id": "ex_...", "exit_code": 0, "stdout": "...", "stderr": "...", "truncated": false, "timed_out": false, "oom_killed": false, "duration_ms": 12, "resume_outcome": "resumed"|"cold_boot", "warnings": ["..."] } // these two only when it woke the machine, or something went differently Port { "port": 3000, "public": true, "default": true, "url": "https://..."|null, "replaced_port": null|8080, "warnings": ["..."] } ``` `:id` in a path is a machine id or its name. - A job's `state` of `lost` means its process went away with its exit status unknown, for example after a reboot. `killed` with `exit_code` 124 means it hit its timeout. - `oom_killed` means the kernel's out-of-memory killer fired while it ran. That's machine-wide, so it's strongest alongside exit code 137. ## Catalog (no auth) `GET /catalog` → `{ "templates": [{name, description, default}], "sizes": [{name, vcpus, memory_mib, default}], "regions": [{code, name, default}], "plans": [...], "rates": {...}, "topups": [...], "exec": {...}, "queue_open": false }` `exec` holds the command limits: `sync_default_seconds` and `sync_max_seconds` (280), `job_default_seconds` (3600), `job_max_seconds` (86400). ## Machines | Method & path | Body / query | Returns | | --- | --- | --- | | `GET /machines` | `?state=running`, `?all=1` to include what's deleted and still remembered | `{ "machines": [Machine] }` | | `POST /machines` | `{ name?, template?, size?, vcpus?, memory_mib?, disk_gib?, region?, snapshot?, setup?, env?, auto_suspend_minutes?, delete_after_minutes?, idempotency_key? }` | `201 Machine` (state `starting`; poll or pass `?wait=1` to get it `running`) | | `GET /machines/:id` | | `Machine` | | `PATCH /machines/:id` | `{ name?, auto_suspend_minutes?, vcpus?, memory_mib?, disk_gib?, delete_after_minutes? }` (CPU/memory only while stopped; disk grows any time; `delete_after_minutes` counts from now, and null cancels it) | `Machine` | | `DELETE /machines/:id` | header `X-Confirm-Delete: ` | `{ "deleted": true, "already_deleted"?: true }` | | `POST /machines/:id/start` | `?wait=0` to answer before it's ready | `Machine` (boots fresh from stopped) | | `POST /machines/:id/stop` | | `Machine` | | `POST /machines/:id/suspend` | | `Machine`, with `suspend_outcome` | | `POST /machines/:id/resume` | `?wait=0` | `Machine`, with `resume_outcome` | | `POST /machines/:id/restart` | `?wait=0` | `Machine` (power-cycle) | | `POST /machines/:id/exec` | `{ cmd?, argv?, cwd?, env?, timeout_ms?, stdin?, background?, tail_bytes? }` | `Exec`; `{ "job": Job }` when `background`; a stream with `Accept: text/event-stream` | | `GET /machines/:id/jobs` | | `{ "jobs": [Job] }` | | `GET /machines/:id/jobs/:job` | `?wait=`, up to 280; ask again if it's still running | `Job` | | `GET /machines/:id/jobs/:job/logs` | `?offset=`, `?tail_bytes=`, `?follow=1` | `text/plain`, `X-Next-Offset` (not with `follow`) | | `POST /machines/:id/jobs/:job/kill` | `{ signal? }`: `TERM`, `KILL` or `INT` | `Job` | | `GET /machines/:id/history` | `?limit=`, 100 by default | `{ "history": [{ id, cmd, argv, cwd, job_id, exit_code, timed_out, oom_killed, cancelled, started_at, finished_at }] }` (works while stopped) | | `GET /machines/:id/history/:exec/output` | | `{ id, job_id, stdout, stderr, truncated, exit_code, timed_out, oom_killed, cancelled }` (the first 16 KiB and last 48 KiB of each stream) | | `GET /machines/:id/ps` | | `{ "processes": [{ pid, ppid, user, command, rss_bytes, cpu_seconds, started_at }] }`. Doesn't wake a machine; answers `machine_not_running` | | `GET /machines/:id/status` | | What the machine's daemon reports: `idle_seconds`, `sessions`, `jobs_running`, `memory`, `data_disk`, `oom_kills` and more. Doesn't wake a machine; answers `machine_not_running` if it isn't running | | `GET /machines/:id/logs` | `?lines=`, `?format=json` | Boot and daemon logs as text. Never wakes a machine: for any other state it answers with its state, error and the provider's events. JSON: `{ machine, name, state, error, logs, events, note }` | | `GET /machines/:id/files` | `?path=` | A directory as `{ path, entries: [{ name, type, size, mode, mtime }] }`, or a file's bytes, always as a download and never rendered | | `PUT /machines/:id/files` | `?path=&mode=`, raw body up to 4 MB | `{ path, size }` | | `DELETE /machines/:id/files` | `?path=&recursive=1` | `{ "deleted": true }` | | `POST /machines/:id/connect` | | `{ url, token, expires_at, machine }`, a 15-minute token for the daemon inside the machine (terminal, TCP tunnels, large files). The CLI is the supported client for what it opens, and the routes behind it can change. Resumes or starts the machine if needed. Connections already open stay open. The CLI uses this for `ssh`, `cp`, `push` and `port-forward`, and they reach it at `*.fly.dev` | | `GET /machines/:id/ports` | | `{ "ports": [Port] }` | | `POST /machines/:id/ports` | `{ port, public? = true }` | `Port` (`url` carries `?box_token=` when private, good for 7 days. A key that can only read, and a webhook, get `url: null` for a private port). A machine can have 10, and an 11th is `limit_reached` | | `DELETE /machines/:id/ports/:port` | | `{ "deleted": true }`. If the URL served that port, it now serves the highest one left | | `POST /machines/:id/snapshots` | `{ name }` | `201 Snapshot` (`creating` if the copy isn't whole when the request answers; usable once `ready`) | | `POST /machines/:id/restore` | `{ snapshot }` | `Machine` (puts the snapshot's disk back; same id, name, URL). `limit_reached` means nothing changed. If something takes the running slot while it works, the disk is restored and the answer is the machine, `stopped`, with `warnings` | | `POST /machines/:id/fork` | `{ count? = 1, name? }`, up to 10 | `{ "machines": [Machine], "failed": [{ name, error }] }` (an error only if none could be made) | Machines auto-resume when you exec, connect, or touch files or jobs on a suspended one, and auto-start when stopped. That restarts their bill, and so the answer says so: `resume_outcome` and `warnings` on an exec or connect (on the `exit` event when streaming), and an `X-Quivvo-Woke: resumed` or `cold_boot` header on files and jobs. Suspend keeps memory and processes when the provider can, and otherwise stops the machine, which keeps only the disk. Both are a success. See [what suspend does](https://tryquivvo.com/docs/machines#what-does-suspend-actually-do). A machine's `url` serves one port, the last published, and answers only while the machine runs: a request to it doesn't wake a suspended machine. ## Running commands - `cmd` runs with `bash -lc`, and `argv` runs directly. Pass exactly one. `cwd` defaults to `/data/home`. - Without `background`, `timeout_ms` is 280000 by default and at most. A command that hits it exits 124 with `timed_out` true. If waking the machine used up part of the request, the command gets less, and `warnings` says so. A waiting command whose caller goes away gets SIGTERM, then SIGKILL 3 seconds later, and its history entry has `cancelled` true. - With `background`, `timeout_ms` is an hour by default and a day at most, and the job goes on if you disconnect. `stdin` can't be combined with it. - `stdin` is text, up to 1 MB. For anything that isn't UTF-8, send `stdin_base64` instead. - `stdout` and `stderr` keep at most 1 MiB each: the first 256 KiB and the last 768 KiB, joined by a line saying how many bytes were left out, with `truncated` true. `tail_bytes`, up to 1 MiB, keeps only the end. - `cmd`, or `argv` in total, is limited to 128 KiB. Put anything longer in a file on the machine. - Pass secrets in `env`. Command text is kept in history, and `env` isn't. - `stdout` and `stderr` are text, so output that isn't UTF-8 comes back with replacement characters, and `warnings` says so. For binary data use the files API, or the stream below. - History keeps a machine's newest 100 commands for 30 days, with the start of any command over 8 KiB, and goes when the machine is deleted. A workspace keeps the newest 8 MiB of it across its machines. A workspace can run 600 commands a minute through the API and make 1,200 changes of any kind, and past that gets `rate_limited`. So does creating more machines in an hour than 60 for each machine its plan runs at once: 60 on Free. With `Accept: text/event-stream` you get Server-Sent Events with no output cap: ```text event: stdout data: {"data":"partial text"} event: stderr data: {"data":"..."} event: exit data: {"id":"ex_...","exit_code":0,"timed_out":false,"oom_killed":false,"duration_ms":812} ``` A chunk that isn't valid UTF-8 has replacement characters in `data` and the bytes the command wrote in `data_base64`. A comment line `: ping` keeps the stream open. `GET .../logs?follow=1` streams until the job ends or for about 280 seconds. If it's still running, follow on from the bytes you've got so far, with `offset`. ## When is a machine deleted? Abandoned machines and snapshots are deleted, after an email warning and a final reminder 2 days before. At zero credit everything goes 7 days after the warning (30 if the workspace has ever paid). On the welcome credit alone, a machine not used for 14 days goes 7 days after the warning, and so do snapshots once no machine in the workspace has been used for that long. Adding credit, resuming the machine or starting a machine from the snapshot cancels it. Active paid plans are exempt. Without a working email sender, nothing is deleted. ## Snapshots | Method & path | Body | Returns | | --- | --- | --- | | `GET /snapshots` | | `{ "snapshots": [Snapshot] }` | | `DELETE /snapshots/:id` | header `X-Confirm-Delete` | `{ "deleted": true, "already_deleted"?: true }` | Create a machine from one with `POST /machines { "snapshot": "base" }`. ## Usage and billing - `GET /usage` → `{ "period_start", "period_end", "plan", "credit_balance", "cost_this_period", "machines": [{ id, name, deleted, running_seconds, cost }], "days": [{ date, cost }], "rates": {...} }`. Amounts are US dollars, to 8 decimal places. - `GET /billing` (session) → plan, balance, ledger. `POST /billing/checkout` (session) `{ kind: "plan", plan } | { kind: "topup", amount }` → `{ url }`. ## Keys (`keys:write` or session) - `GET /keys` → `{ "keys": [{ id, name, kind, prefix, scopes, created_at, last_used_at }] }` - `POST /keys` `{ name, scopes? }` → `{ "key": {...}, "api_key": "qvk_..." }` (the secret is shown once) - `DELETE /keys/:id` → `{ "deleted": true }`. Any key can revoke itself, with or without `keys:write`. ## Device sign-in (CLI and agents) 1. `POST /auth/device` `{ "kind": "cli"|"agent", "name": "agent@host", "scopes"?: [...] }` (no auth) → `{ "device_code", "user_code": "ABCD-EFGH", "verification_url", "verification_url_complete", "expires_in", "interval" }`. `cli` codes live 15 minutes, `agent` codes 24 hours. 2. The owner opens `verification_url_complete` signed in and approves, unticking any scopes, or runs `quivvo claim ABCD-EFGH` (needs `keys:write`), which calls `POST /auth/device/approve { user_code, scopes? }`. Without `scopes`, it grants what the sign-in asked for except `keys:write`, which has to be asked for by name. A key can't grant a scope it doesn't have. `POST /auth/device/deny { user_code }` refuses it. `GET /auth/device/request?code=` shows what's being approved, with `created_at` and `started_from`. A workspace can make 120 of these three calls an hour. 3. The device polls `POST /auth/device/token { device_code }` every `interval` seconds: `400 { error: { code: "authorization_pending" } }`, `400 access_denied`, `400 expired_token`, or `200 { "api_key": "qvk_...", "key": {...}, "workspace": { id, name }, "queue": {...} }`. Asking again within 10 minutes, if that answer got lost, returns a new key and revokes the first. That stops working once the key has been used or revoked, and a sign-in only ever holds one live key. See [agent sign-up](https://tryquivvo.com/docs/agents) for how an agent does this without blocking. ## Webhooks - `GET /webhooks`, `POST /webhooks { url, events? }` → `201 { webhook, secret }`, `DELETE /webhooks/:id`. - `url` must be public `https`. Private, loopback and link-local addresses, internal names and a username or password in the URL are refused, and a name that resolves to a private address isn't delivered to. Redirects aren't followed. - Events: `machine.ready`, `machine.suspended`, `machine.stopped`, `machine.failed`, `machine.deleted`. - `POST` to your URL with `{ "id", "type", "created_at", "data": { "machine": Machine } }` and header `Quivvo-Signature: t=,v1=`. ## Feedback `POST /feedback` `{ message, machine?, context? }` → `201 { id, received: true }`. `message` is up to 4000 bytes, `context` is any JSON up to 16 KiB, and a workspace can send 30 an hour. It keeps its newest 1 MiB of them, for 90 days. Any valid key can use it. Include a `request_id` if an error gave you one, and leave out secrets. ## MCP Streamable HTTP at `https://tryquivvo.com/api/mcp`. Clients sign in with OAuth at the first tool call, or send `Authorization: Bearer qvk_...`. See [MCP server](https://tryquivvo.com/docs/mcp) for its tools. --- # SDKs Quivvo has SDKs for TypeScript and Python. Both wrap the [REST API](https://tryquivvo.com/docs/api), have no dependencies, and follow the API's rules for you: an `Idempotency-Key` on every lifecycle call, retries only when they're safe, and the API's error object as an exception. ## TypeScript ```bash npm install quivvo-sdk ``` ```ts import { Quivvo } from "quivvo-sdk"; const q = new Quivvo(); // reads QUIVVO_API_KEY const m = await q.machines.create({ name: "dev" }); const r = await m.exec("uname -a"); console.log(r.exit_code, r.stdout); for await (const ev of q.machines.execStream("dev", "npm test")) { if (ev.event === "stdout") process.stdout.write(ev.data); } await q.machines.suspend("dev"); ``` It uses only `fetch`, so it runs on Node.js 18 or later, Bun, Deno and edge runtimes. Every method takes `{ signal, timeoutMs, maxRetries }` last. ## Python ```bash pip install quivvo ``` ```python from quivvo import Quivvo q = Quivvo() # reads QUIVVO_API_KEY m = q.machines.create(name="dev") r = m.exec("uname -a") print(r["exit_code"], r["stdout"]) for ev in q.machines.exec_stream("dev", "npm test"): if ev["event"] == "stdout": print(ev["data"], end="") q.machines.suspend("dev") ``` It needs Python 3.9 or later and uses only the standard library. ## What do they cover? Everything a key can do over the API: machines and their lifecycle, commands (waiting for the result or streaming it), background jobs and their logs, files, ports, snapshots, forks and restores, keys, webhooks, usage, `whoami` and the catalog. Device sign-in isn't in them: get a key with `quivvo login` or from [API keys](https://tryquivvo.com/app/keys), and pass it as `apiKey` / `api_key` or set `QUIVVO_API_KEY`. ## How do errors work? An error the API sends raises `QuivvoError`, with the same fields as the [error object](https://tryquivvo.com/docs/errors): `code`, `message`, `retryable`, the seconds to wait, `docs_url`, `request_id` and `owner_action`. Branch on `code`. A request that got no answer raises `QuivvoNetworkError`, which says whether the request may have reached the server, so you know whether repeating it is safe. A command that exits non-zero isn't an error: read its exit code. A command is only sent again when the API says it didn't run. --- # MCP server Quivvo has a hosted MCP server, so coding agents like Claude Code, Cursor and Codex can create and drive cloud machines directly. It speaks Streamable HTTP at `https://tryquivvo.com/api/mcp`. You sign in through your browser, or pass a Quivvo API key. ## How do I set it up? Add the server and sign in when your client asks. No key to paste: ```bash claude mcp add --transport http quivvo https://tryquivvo.com/api/mcp ``` In Cursor or VS Code: [add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=quivvo&config=eyJ1cmwiOiJodHRwczovL3RyeXF1aXZ2by5jb20vYXBpL21jcCJ9), or [install in VS Code](vscode:mcp/install?%7B%22name%22%3A%22quivvo%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Ftryquivvo.com%2Fapi%2Fmcp%22%7D). Each opens the client with this server filled in. In Claude Code, run `/mcp` to sign in. Your browser shows which app is asking and where it will send you back. Read and write are ticked, and deleting is off unless you tick it. The app gets a key that works only on this server, listed under [API keys](https://tryquivvo.com/app/keys) until you revoke it. It doesn't expire, and there's no refresh token: if it's revoked, the client asks you to sign in again. For CI, or a client that can't sign in, use an API key. The CLI writes that configuration for you: ```bash quivvo mcp init claude quivvo mcp init cursor quivvo mcp init codex ``` The config refers to `$QUIVVO_API_KEY` rather than holding your key, so it's safe in a repo and the key stays out of transcripts. Set that variable wherever your client starts from, then start a new session. If you've signed in with the CLI, this reads the saved key without showing it: ```bash export QUIVVO_API_KEY="$(node -p "require('$HOME/.config/quivvo/credentials.json').api_key")" ``` Add `--project` to write a project file instead of your user configuration. `--inline-key` writes the key itself instead of a reference, and that file must stay out of git. `quivvo init` does the same for every AI tool it finds, and installs the [skill](https://tryquivvo.com/skill.md) too. To add it by hand, Claude Code takes: ```bash claude mcp add --transport http --scope user quivvo https://tryquivvo.com/api/mcp --header 'Authorization: Bearer ${QUIVVO_API_KEY}' ``` Cursor, in `~/.cursor/mcp.json`: ```json { "mcpServers": { "quivvo": { "url": "https://tryquivvo.com/api/mcp", "headers": { "Authorization": "Bearer ${env:QUIVVO_API_KEY}" } } } } ``` Codex, in `~/.codex/config.toml`. Codex gives up on a tool call after 60 seconds by default, so raise that if you'll ask `exec` to wait longer than its default: ```toml [mcp_servers.quivvo] url = "https://tryquivvo.com/api/mcp" bearer_token_env_var = "QUIVVO_API_KEY" tool_timeout_sec = 300 ``` For a client that only supports stdio, `quivvo mcp start` runs a local server that forwards to the hosted one. These setups follow each client's own documentation, and clients change. We test the server with the MCP SDK's client. If one of the above doesn't work in yours, tell us with `report_problem` or `quivvo feedback`. ## How do I call it by hand? A client does this for you. With `curl`, `tools/list` gives each tool's argument names, such as `cmd` for `exec` and `confirm` for `delete_machine`. A request with no protocol version needs no more than the key: ```bash curl -s https://tryquivvo.com/api/mcp -H "Authorization: Bearer $QUIVVO_API_KEY" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"exec","arguments":{"machine":"work","cmd":"uname -a"}}}' ``` `2026-07-28` wants its version in the headers and in `params._meta`, along with the client's capabilities. `Mcp-Method` and, for `tools/call`, `Mcp-Name` have to repeat what the body says, or the answer is error `-32020`: ```bash curl -s https://tryquivvo.com/api/mcp -H "Authorization: Bearer $QUIVVO_API_KEY" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2026-07-28' -H 'Mcp-Method: tools/call' -H 'Mcp-Name: exec' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"exec","arguments":{"machine":"work","cmd":"uname -a"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' ``` ## What tools does it have? | Tool | What it does | | --- | --- | | `create_machine` | Creates a machine and waits until it runs. Takes `env`, `disk_gib`, `auto_suspend_minutes` and `delete_after_minutes` | | `list_machines`, `get_machine` | Lists machines, or shows one, including how its setup script went and what its last suspend turned into | | `exec` | Runs a shell command as root and returns its exit code and output | | `job_status`, `job_logs`, `job_kill`, `list_jobs` | Follows, reads, stops and finds commands that are still running | | `read_file`, `write_file` | Reads a file or lists a directory, or writes a text file up to 4 MB | | `start_machine`, `stop_machine`, `restart_machine` | Boots, shuts down or reboots a machine | | `suspend_machine`, `resume_machine` | Suspends, falling back to a stop when it can't keep memory, or resumes. `suspend_outcome` and `resume_outcome` say which happened | | `fork_machine` | Copies a machine's disk into new machines. `failed` lists any that couldn't be made | | `publish_port`, `unpublish_port`, `list_ports` | Gives a port a public HTTPS URL, or takes it away. The URL serves one port, the last published, and `replaced_port` says which it stopped serving | | `save_snapshot`, `list_snapshots`, `delete_snapshot` | Saves, lists or deletes disk snapshots. Deleting needs the exact name to confirm | | `delete_machine` | Deletes a machine and its disk; needs its exact name to confirm | | `get_usage`, `get_catalog`, `whoami` | Shows cost and credit; templates, sizes, regions and prices; what this key may do and its place in the queue | | `report_problem` | Tells our team something went wrong | Before anyone signs in, a client can see all of these tools, and the first call to one starts sign-in. After that, an agent only sees the tools its key's scopes allow. A key from [agent sign-up](https://tryquivvo.com/docs/agents) can't manage keys or billing. ## How do long commands work? `exec` always starts a job, so the command outlives the call, and waits for its result: - `wait_seconds` is 50 by default, at most 280, and 0 answers once it has started. Keep it under your client's tool timeout. - If it finishes in time, you get its result. If not, you get `job_id`, the output so far, `next_offset`, and a `next` instruction. Don't run it again. - `timeout_seconds` kills it after that long: 3600 by default, at most 86400. - If your client hangs up during the wait, the command is stopped with TERM, then KILL, and history marks it `cancelled`. Otherwise it couldn't be found again, and a retry would run it twice. - Output is one merged `output` field, stdout and stderr together. `tail_bytes` returns only the end. Pass secrets in `env`: it stays out of history, and the command text doesn't. Follow it with `job_status` (`wait_seconds` up to 60, default 0), `job_logs` (`offset`, `tail_bytes`, and `next_offset` to read on) and `job_kill` (`TERM`, `INT` or `KILL`). `list_jobs` finds one whose id you lost. Other long calls, such as `create_machine`, start, resume, restart, stop, suspend, `save_snapshot` and `fork_machine`, answer at about 50 seconds with where things got to and a `next` field, and the work carries on. ## Which tools wake a machine? `exec`, `write_file`, `start_machine`, `resume_machine` and `restart_machine` do. Reading never does, because a tool marked read-only shouldn't start a bill. `read_file`, `job_status`, `job_logs`, `list_jobs` and `job_kill` answer `machine_not_running` on a machine that isn't running, naming the tool to call. This differs from the REST API, where reading files and jobs wakes a machine. If a machine is still waking, `exec` and `write_file` answer `machine_not_running` with `retry_after_seconds` and say nothing has run. When one of them did wake it, the answer has `resume_outcome`, and `warnings` if it was a cold boot. ## How big are the answers? Default answers stay under 32,000 characters. Long command output keeps its start and end, with `[... N bytes omitted ...]` where the middle was cut. `truncated` and `more` say how to fetch the rest, and lists page with `offset`. `max_bytes` and `tail_bytes` can ask for up to 1 MiB. `read_file` returns `encoding` as `utf-8` or `base64`, with `size`, `offset` and `next_offset`. ## What do errors look like? JSON text with `isError` set, and the same fields as the [REST API](https://tryquivvo.com/docs/errors): `code`, `message`, `retryable`, `retry_after_seconds`, `docs_url`, `owner_action` and `request_id`. Unknown arguments are refused, naming the valid ones. A workspace waiting in the queue gets [`queued`](https://tryquivvo.com/docs/errors#queued) from `create_machine` and `fork_machine`: pass `owner_action` to your owner, and don't retry. ## How does sign-in work? It's OAuth 2.1 with PKCE (S256 only), for public clients that register themselves. A call with no valid token answers HTTP 401 with `WWW-Authenticate: Bearer resource_metadata=...`, which leads to the [protected resource metadata](https://tryquivvo.com/.well-known/oauth-protected-resource/api/mcp) and then the [authorization server metadata](https://tryquivvo.com/.well-known/oauth-authorization-server). Redirect URIs must be `https`, or `http` on `localhost`, `127.0.0.1` or `[::1]`, where any port works. `state` is required, and a token is bound to `https://tryquivvo.com/api/mcp`, so it doesn't work on the REST API. ## Which protocol versions does it speak? Both eras of the protocol share the endpoint: `2026-07-28`, where every request carries its own version and `server/discover` needs no key, and the older `initialize` handshake from `2025-11-25`, `2025-06-18`, `2025-03-26` and `2024-11-05`. It's stateless, with JSON responses and no sessions. Its [server card](https://tryquivvo.com/api/mcp/server-card) says how to connect. --- # Agent sign-up An AI agent with no browser and no credentials can sign itself up for Quivvo. It gets a code to show its owner, and nothing exists until the owner approves. For the whole setup, including MCP and a verifying run, follow [the setup runbook](https://tryquivvo.com/setup.md). ```bash command -v quivvo >/dev/null || npm i -g quivvo@latest quivvo login --agent --no-wait --json ``` That never blocks, so it's safe in a tool call with a short timeout. The first run prints one JSON document and exits 0: ```json { "status": "pending", "user_code": "ABCD-EFGH", "verification_url_complete": "https://tryquivvo.com/device?code=ABCD-EFGH", "expires_in": 86400, "message_for_owner": "Please approve my access to Quivvo: open ... and confirm the code ABCD-EFGH. ...", "next_command": "quivvo login --agent --no-wait --json" } ``` Send the owner `message_for_owner` and end your turn. Don't sleep or poll: they may be gone for hours, and the code lasts 24 hours. Run the same command again later. It checks once and exits: | Exit | Meaning | What to do | | --- | --- | --- | | `0` | `status: "approved"`: the key is saved | Carry on | | `75` | Still pending; the document repeats the code | End your turn and try later | | `76` | The code expired | Run it again for a new code | | `77` | The owner denied it | Ask them before trying again | New accounts wait in a queue for now. Run `quivvo whoami --json`: if `queue.waiting` is true, tell your owner `queue.position` and `queue.invite_url`, and stop. Add `--name` to label the key, which is `agent@` by default, and `--scope` to ask for fewer scopes. Without `--no-wait`, `quivvo login --agent` prints the same message and waits for approval, which only suits something that can sit still for a long time. ## How does the owner approve it? The owner opens the link while signed in, checks that the code matches, unticks any scopes they don't want, and approves. From a terminal where they already use Quivvo, they can run: ```bash quivvo claim ABCD-EFGH ``` That takes a key with `keys:write`. A CLI signs in without it unless they tick it when approving, so the link is the surer way. The agent then holds an ordinary API key of the owner's workspace, saved in `~/.config/quivvo/credentials.json`. ## What can an agent's key do? | Scope | Allows | | --- | --- | | `machines:read` | Listing and reading machines, history, jobs, files and usage. It reads every ordinary file on a machine, but not `/proc`, `/sys`, `/dev`, `/run` or the daemon's own state | | `machines:write` | Creating machines, running commands as root, writing files, lifecycle, ports, snapshots | | `machines:delete` | Deleting machines and snapshots, and setting a deadline for deleting a machine that already exists | Agent keys can never manage API keys or billing. `quivvo whoami` shows what a key may do. When the answer is that only the owner can do something, the error carries `owner_action` to pass on. The owner can see agent keys under API keys in the dashboard and revoke them at any time. An agent that's finished can revoke its own with `quivvo logout --revoke`. ## How do I do this over HTTP? 1. `POST https://tryquivvo.com/api/v1/auth/device` with `{"kind":"agent","name":"agent@host"}` returns `device_code`, `user_code`, `verification_url_complete`, `expires_in` and `interval`. 2. Show the owner `verification_url_complete` and `user_code`, and keep `device_code` somewhere that survives your turn. 3. Later, `POST https://tryquivvo.com/api/v1/auth/device/token` with `{"device_code":"..."}`, at most every `interval` seconds. You get `authorization_pending` until the owner decides, then `api_key`, or `access_denied` or `expired_token`. If the answer holding `api_key` got lost, ask again within 10 minutes. You get a new key, and the lost one is revoked. That stops working once the key has been used, or once the owner has revoked it. ## Is there a skill I can install? Yes. [`/skill.md`](https://tryquivvo.com/skill.md) teaches an agent how to use Quivvo. `quivvo init` installs it, along with the MCP server, for the AI tools it finds on this computer. It shows a checklist first, and changes nothing without `--yes` or a confirmation at a terminal. ```bash quivvo init --yes --client claude ``` --- # Errors Every API error has the same shape, and `docs_url` points at the matching section of this page. The exception is a path that exists asked with a method it doesn't take: that's a 405 with no body, from before our code runs. ```json { "error": { "code": "limit_reached", "message": "The Free plan runs 1 machine at a time, and dev is already running. Suspend one or upgrade.", "retryable": false, "retry_after_seconds": null, "docs_url": "https://tryquivvo.com/docs/errors#limit_reached", "request_id": "req_...", "owner_action": { "url": "https://tryquivvo.com/app/billing", "message": "Upgrade from the Free plan to run more machines at once." } } } ``` | Field | What to do with it | | --- | --- | | `code` | Branch on this. The message can change | | `retryable`, `retry_after_seconds` | When `retryable` is true, wait that long and send the same request again | | `request_id` | Quote it when you report a problem. It's also the `x-request-id` header | | `owner_action` | Something only the workspace's owner can do, signed in on the dashboard. Pass on `message` and `url`: an API key can't do it | The CLI turns these into exit codes. Under `--json` every error is one document on stdout, with the same fields plus `exit_code`, `status` and `hint`. The MCP server returns the same fields as JSON text with `isError` set. ## `bad_request` HTTP 400. CLI exit 2. Don't retry as it is. The request is wrong: a bad field, a size that isn't allowed, a missing `X-Confirm-Delete` header, or an upload over 4 MB. The message says what to change. ## `unauthorized` HTTP 401. CLI exit 3. Don't retry as it is. There's no key, or it's invalid or was revoked. Sign in again with `quivvo login --agent --no-wait --json`, or check `QUIVVO_API_KEY`. See [agent sign-up](https://tryquivvo.com/docs/agents). ## `forbidden` HTTP 403. CLI exit 3. Don't retry as it is. This key doesn't have the scope, a read-only key asked for a path it can't read (`/proc`, `/sys`, `/dev`, `/run` or the daemon's own state), the route needs someone signed in on the dashboard, as billing does, or a signed-in browser's request came from another site. `GET /api/v1/whoami` lists the scopes a key has. `owner_action` says what the owner can do. ## `not_found` HTTP 404. CLI exit 4. Don't retry as it is. There's no such machine, snapshot, job or file in this workspace. `quivvo ls` shows the machines it has. ## `conflict` HTTP 409. CLI exit 75 when `retryable`, otherwise 1. From `exec`, `run` and `sh`, 125. The name is taken, the machine is between states or still being created, its CPU or memory can't change while it runs, a snapshot isn't ready, a disk is still being copied from what you're deleting, or an `Idempotency-Key` was reused for a different request, including the same route with a different body. When `retryable` is true, the machine is between states and will settle: try again. Otherwise the message says what to change. ## `limit_reached` HTTP 403. CLI exit 5. Don't retry as it is. A plan limit: machines running at once, machines kept, size, disk, snapshots, how long a machine may stay awake, active keys, webhooks, or published ports on a machine. The message names the limit and what's in the way. Make room with what you're allowed to do: suspend or delete a machine, or delete a snapshot. The CLI's hint names the commands. A request that's wrong in itself, such as a bad name or a name already taken, gets that error first and never this one. Upgrading is for the owner: pass on `owner_action`. ## `insufficient_credits` HTTP 402. CLI exit 5. Don't retry as it is. The credit balance is empty, so machines can't run. Only the owner can add credit, on the dashboard: pass on `owner_action`. API keys can't reach billing. ## `queued` HTTP 403. CLI exit 5. Don't retry as it is. This workspace is waiting in the queue, so it can't create or fork machines yet. The message gives its place. Only the owner can move it: pass on `owner_action`, which holds their invite link. Each person who signs up with the link moves them up 10 places, and a paid plan skips the queue. `quivvo queue` and [`GET /queue`](https://tryquivvo.com/docs/api#the-queue) show the same. ## `machine_not_running` HTTP 409. CLI exit 75 when `retryable`, otherwise 1, or 125 from `exec`, `run` and `sh`. The machine isn't running, and nothing ran. With `retry_after_seconds` it's on its way up: try again, as the CLI does by itself. Without it, it stays down until asked: run `quivvo start` or `quivvo resume`, or for a machine in `failed`, `quivvo restart`. The MCP server's read tools answer this for any machine that isn't running, because they don't wake machines, and so does the REST API for a key with no `machines:write`. ## `provider_error` HTTP 502. CLI exit 75 when `retryable`, otherwise 1. From `exec`, `run` and `sh`, 125. The machine's host, or its daemon, didn't do what was asked. The command may or may not have run, so check before repeating anything that changes things. `quivvo logs` shows what's known about a machine, whatever its state. A snapshot or fork that the provider won't copy ends here too. In that case nothing was created, and the source is untouched. ## `capacity` HTTP 503. CLI exit 75. Retry after `retry_after_seconds`. There's no room right now, on our side or the provider's. On a create, nothing was created: try another region with `--region`, or try again in a few minutes. On a start or resume, the machine and its disk are where they were, and no other host in its region had room either: try again in a few minutes. ## `rate_limited` HTTP 429. CLI exit 75. Retry after `retry_after_seconds`. Too many requests, such as more than 30 feedback reports in an hour, more than 600 commands or 1,200 changes of any kind a minute in a workspace, more machines created in an hour than a plan allows (60 on Free), or too many sign-ins started from one address. Nothing happened. Wait and send it again. A sign-in that's already pending doesn't count: run the same `login` again and it reuses its code. ## `internal` HTTP 500. CLI exit 75 when `retryable`, otherwise 1. From `exec`, `run` and `sh`, 125. Something went wrong on our side. The request may or may not have taken effect, so check first if repeating it would matter. If it keeps happening, report it with the `request_id`: `quivvo feedback "what happened"`. ## `authorization_pending` HTTP 400, while polling `POST /api/v1/auth/device/token`. `quivvo login --agent --no-wait` exits 75. The owner hasn't decided yet. Try again later, no sooner than every `interval` seconds. An agent should end its turn rather than wait. ## `access_denied` HTTP 400, while polling. `quivvo login` exits 77. The owner refused this sign-in. Ask them before trying again. Nothing was created. ## `expired_token` HTTP 400, while polling. `quivvo login` exits 76. The code expired before anyone approved it, or this sign-in was already completed. Agent codes last 24 hours and CLI codes 15 minutes. Run the command again for a new code. ## What other codes can the CLI report? Under `--json`, a failure that never reached the API has a code of its own: | Code | Exit | Meaning | | --- | --- | --- | | `usage` | 2 | The command line was wrong. `quivvo help --json` lists every command and flag | | `not_signed_in` | 3 | There's no key. The hint names the sign-in command | | `no_terminal` | 3 | `quivvo login` needs a person at a terminal. The hint says what to run instead | | `network_error` | 75, 1 or 125 | The API couldn't be reached. 75 means nothing happened; 1 or 125 means it may have | | `timeout` | 124 | A wait ran out. What you were waiting for keeps going | | `agent_not_installed`, `agent_not_signed_in` | 3 | `quivvo agent prompt`: the message gives the exact fix | | `confirmation_required` | 2 | Something destructive needs `--yes` when there's no terminal to ask on | | `setup_failed` | 1 | `create --wait-setup`: the script failed, and the machine is still there and billing. The hint has its log and how to delete it | | `machine_failed` | 1 | A machine being waited for went to `failed`. `quivvo logs` shows what's known | | `unexpected_state` | 1 | A machine being waited for settled in a state nobody asked for | | `snapshot_failed` | 1 | `snapshot save`: the copy didn't finish | | `stream_lost` | 125 | `exec`: the stream ended before the command did, so its result is unknown. `quivvo history` shows what's known | | `command_lost` | 125 | `run`: the machine restarted while the command ran, so its result is unknown | | `nothing_to_push` | 2 | `push`: every file in the directory is left out. The hint says how to send them | | `tar_failed` | 1 | `cp` or `push`: the local `tar` failed | | `no_desktop` | 1 | `desktop`: the machine's image has none. A new machine on the `universal` template has one | | `bad_config` | 1 | `mcp init`: an existing config file can't be read, and it wasn't touched | | `client_failed` | 1 | `mcp init`: the client's own command failed. The hint has the command to run yourself | | `no_session` | 1 | `agent prompt --continue`: that run has no session to carry on from | | `preflight_failed` | 1 | `agent prompt`: the check before starting failed. The message says why | `exec`, `run`, `ssh`, `jobs wait` and `jobs logs -f` pass on the remote command's exit code, which can be any of these numbers too. Under `--json`, the `error` key is what tells them apart. --- # Webhooks Quivvo can send a signed HTTP POST to your server when a machine changes state, so you don't have to poll. Register a URL with the API, and keep the secret it returns. ```bash curl -X POST https://tryquivvo.com/api/v1/webhooks \ -H "Authorization: Bearer $QUIVVO_API_KEY" -H "content-type: application/json" \ -d '{"url":"https://example.com/hooks/quivvo"}' ``` A workspace can have 10 webhooks. The URL must be public `https`, up to 2048 characters. Private, loopback and link-local addresses, internal names and a username or password in the URL are refused, and a name that resolves to a private address isn't delivered to. Redirects aren't followed, so point it at the final address. ## What events are there? | Event | When | | --- | --- | | `machine.ready` | A machine finished booting | | `machine.suspended` | A machine suspended and kept its memory, by request or because it was idle | | `machine.stopped` | A machine stopped, including a suspend that couldn't keep memory and became a stop | | `machine.failed` | A machine failed to boot or crashed | | `machine.deleted` | A machine was deleted | Pass `events` when registering to receive only some of them. ## How do I verify a delivery? Each POST carries a `Quivvo-Signature: t=,v1=` header. Compute HMAC-SHA256 of `t + "." + body` with your secret and compare it to `v1`. The body looks like this: ```json { "id": "evt_...", "type": "machine.ready", "created_at": "...", "data": { "machine": {} } } ``` `data.machine` is the same object the [REST API](https://tryquivvo.com/docs/api#objects) returns. Deliveries are best effort and aren't retried yet, so treat webhooks as a hint and read the machine's state when it matters. --- # Privacy Quivvo is run by Altria LLC ("we"). This page says what we collect when you use tryquivvo.com, the Quivvo CLI, the REST API and the MCP server, why we collect it, and who else handles it. ## What do we collect? - **Your account:** your email address. If you sign in with GitHub or Google, the account ID and email address they send us. We never see a password. - **Your workspace:** your machines, snapshots, API keys (stored only as a hash), your place in the queue and how many people joined with your invite link, your plan, your credit and usage per machine and day. - **What you run:** commands sent through the API and their output, kept up to a size limit per machine and deleted with the machine, plus setup scripts and published ports. The files on a machine's disk are yours. We don't look at them unless running or protecting the service needs it, or the law requires it. - **Payments:** Stripe handles them. We keep the Stripe customer ID and what was paid, never card numbers. - **Technical data:** IP addresses and request details in server logs. For rate limits we store a hash of the IP address, not the address itself. We count how often the Markdown docs are read by kind of client, such as "browser" or "claudebot", and nothing about who read them. - **Visits to tryquivvo.com,** counted by us, with no cookies and no third party: the page, the site that linked to it, the campaign tags in the link (such as `utm_source`), the country and whether it's a phone or a computer. A visitor is a hash of the IP address, the browser and the day, salted with a secret, so it changes every day and can't be turned back into either. The IP address isn't stored. When you sign up, we note which visit brought you. The dashboard isn't counted, and neither is a browser that sends Global Privacy Control. - **Invites:** when we invite someone to skip the queue, their email address, to send the invite and let them in when they sign up with it. - **Problem reports** that you or your agent send us. ## Why do we collect it? To run the service and sign you in, to bill you, to see which pages and links bring people to Quivvo, to run the queue, to stop abuse (the welcome credit is once per mailbox, for example), to send service emails (sign-in links, "it's your turn", warnings before an abandoned machine is deleted) and to fix problems. For people in the EU and UK, the legal bases are the contract with you, our legitimate interest in keeping the service secure and free of abuse, and legal obligations. We show no ads, use no tracking or analytics cookies, and don't sell your data. ## Which cookies do we use? One: the sign-in session cookie, which keeps you signed in. Without it the dashboard can't work. ## Who else handles your data? | Service | What for | Where | |---|---|---| | Supabase | Database and sign-in | EU (Frankfurt) | | Vercel | Hosting the site and the API | Global, with functions in the US | | Fly.io | Running your machines | The region you choose | | Resend | Sending email | EU (Ireland) | | Stripe | Payments | US and EU | | GitHub, Google | Sign-in, only if you use them | US | Where data leaves the EU or UK, it goes under each provider's data processing terms. ## How long do we keep it? - **Your account:** while it exists. Ask us to delete it and we remove it within 30 days, except billing records the law requires us to keep. - **Deleted machines and snapshots:** their records are removed 35 days after deletion. A machine's command history goes when the machine does. - **Problem reports:** 90 days. - **The welcome credit check:** a one-way hash of each mailbox that has had the credit, kept so the credit can't be claimed twice. It isn't your address and can't be turned back into it. ## What are your rights? You can ask to see, correct, export or delete your data, and you can object to how we use it. Email hello@tryquivvo.com. If you're in the EU or UK, you can also complain to your data protection authority. ## Changes When this page changes, the date at the bottom of it changes too. Contact: Altria LLC, hello@tryquivvo.com. --- # Quivvo pricing Quivvo bills running machines by the second: $0.005 per vCPU-hour and $0.011 per GiB-hour of memory, plus $0.20 per GiB-month of disk while a machine exists. A 2 vCPU, 4 GiB machine costs $0.054 an hour while it runs. ## What does a machine cost per hour? | Size | vCPUs | Memory | Per running hour | | --- | --- | --- | --- | | `small` | 1 | 2 GiB | $0.027 | | `medium` (default) | 2 | 4 GiB | $0.054 | | `large` | 4 | 8 GiB | $0.108 | | `xlarge` | 8 | 16 GiB | $0.216 | Suspended and stopped machines pay only for disk. Disk is charged per GiB-month, so a 10 GiB disk costs $2.00 a month. Snapshots are charged at the same rate for the size of the disk they copied, from when they're ready until you delete them. ## What are the plans? | Plan | Price | Credit | Running at once | Machines | Largest machine | Disk per machine | Snapshots | | --- | --- | --- | --- | --- | --- | --- | --- | | Free | Free | $5 once | 1 | 3 | 2 vCPUs, 4 GiB | Up to 20 GiB | 3 | | Pro | $20 a month | $20 a month | 25 | 200 | 8 vCPUs, 16 GiB | Up to 200 GiB | Unlimited | | Team | $100 a month | $100 a month | 100 | 1000 | 8 vCPUs, 16 GiB | Up to 500 GiB | Unlimited | Credit is US dollars. Usage comes off your balance every minute, and plans add credit each month. Credit doesn't expire. Top-ups come in $10, $25, $50 and $100. New accounts wait in a queue for now, and the credit arrives with their turn. Each person who signs up with your invite link moves you up 10 places, and a paid plan skips it. ## What happens when credit runs out? Running machines are suspended, or stopped where suspending can't be done, and you can start them again once you top up. If credit stays empty, we email you, then delete your machines and snapshots 7 days later, or 30 days if you've ever paid. Disks and snapshots are charged until then, so a balance can dip below zero. See [machines](https://tryquivvo.com/docs/machines) for the full rules. ## How does this compare? See the [agent sandbox pricing comparison](https://tryquivvo.com/compare/sandbox-pricing), with sources for every number. --- # Compare Quivvo These pages compare Quivvo with other cloud sandboxes for AI agents, using each vendor's own published prices. Every number links to its source and has a date, and we say when another product is the better choice. - [Agent sandbox pricing comparison](https://tryquivvo.com/compare/sandbox-pricing): what the same machine costs across seven providers. - [Quivvo vs InstaBox](https://tryquivvo.com/compare/instabox) - [Quivvo vs boat](https://tryquivvo.com/compare/boat) - [Quivvo vs E2B](https://tryquivvo.com/compare/e2b) - [Quivvo vs Daytona](https://tryquivvo.com/compare/daytona) - [Quivvo vs Fly Sprites](https://tryquivvo.com/compare/fly-sprites) --- # AI agent sandbox pricing compared A machine with 2 vCPUs and 4 GiB of memory costs between $0.017 and $0.166 per running hour across seven agent sandbox providers, a tenfold range. InstaBox and boat are the cheapest at list price, at $0.017 and $0.018, Quivvo costs $0.054, and E2B and Daytona are the most expensive at about $0.166. Prices were checked on 30 October 2026 from each vendor's own pricing page, E2B's and Daytona's again on 31 October, and InstaBox's and boat's on 7 November. ## What does the same machine cost? | Provider | Per vCPU-hour | Per GiB-hour of memory | 2 vCPU + 4 GiB, per hour | Disk | | --- | --- | --- | --- | --- | | [InstaBox](https://instabox.sh/docs/pricing.md) | $0.004 | $0.00225 | **$0.017** | 20 GiB on Free. Pro includes 200 GiB across machines, then $0.08 per GiB-month | | [boat](https://docs.boat.dev/pricing) | Priced per size | Priced per size | **$0.018** | 51 GB included | | [Quivvo](https://tryquivvo.com/pricing) | $0.005 | $0.011 | **$0.054** | $0.20 per GiB-month, none included | | [Northflank](https://northflank.com/pricing) | $0.01667 | $0.00833 | **$0.067** | $0.11657 per GiB-month for persistent disk | | [Fly Sprites](https://fly.io/sprites) | $0.0385 of actual use | $0.021875 of actual use | **up to $0.165** | About $0.50 per GB-month awake, $0.02 asleep | | [E2B](https://e2b.dev/pricing) | $0.0504 | $0.0162 | **$0.166** | Included: 10 GiB on Hobby, 20 GiB on Pro | | [Daytona](https://www.daytona.io/pricing) | $0.0504 | $0.0162 | **$0.166** | About $0.08 per GiB-month, first 5 GiB free | The hourly column is our arithmetic from each vendor's published rates, except boat's, which is its published price for its 2 vCPU, 4 GB size. Fly Sprites bills for actual use rather than allocation, so its figure is the ceiling for a machine busy the whole hour. ## What does it cost over a month? | Provider | Running 2 hours a day (60 hours) | Running all month (730 hours) | | --- | --- | --- | | InstaBox | $1.02 | $12.41 | | boat | $1.08 | $13.14 | | Quivvo | $3.24 | $39.42 | | Northflank | $4.00 | $48.66 | | E2B | $9.94 | $120.89 | | Daytona | $9.94 | $120.89 | These are running costs only, before disk and any plan fee. Fly Sprites is left out because its bill depends on how busy the machine is. ## Why isn't this like for like? - **CPU differs.** Quivvo's vCPUs are shared, with a baseline when a machine stays busy for long stretches. boat's pricing page says its vCPUs are shared too. Other vendors' CPU policies aren't stated on their pricing pages. - **Idle time differs.** A paused or suspended machine usually stops paying for CPU, so how quickly a product pauses matters as much as its rate. Fly Sprites doesn't bill compute while asleep. Quivvo and InstaBox charge only for disk while paused. boat has no pause: a stop ends running processes and keeps the disk. - **Session limits differ.** E2B's pricing page limits sessions to 1 hour on Hobby and 24 hours on Pro. InstaBox's Free plan runs a machine for up to 8 hours at a time. boat stops a sandbox an hour after it starts or resumes unless you set it otherwise. - **Plans differ.** Several vendors include credit in a monthly fee, and credit may or may not roll over. InstaBox's resets monthly; Quivvo's doesn't expire. boat's plans start at $20 a month after a 7-day trial that asks for a card, and its plan price comes back as machine time. - **Regions differ.** boat runs in Germany, Finland and France. InstaBox chooses where a machine runs and refuses a `region` field. Quivvo lets you pick Amsterdam or Ashburn, Virginia. ## Which should I choose? If list price is what matters most, InstaBox and boat are the cheapest here, at about a third of Quivvo's price. If you want to pay only for the CPU you actually use, look at Fly Sprites. If you need enterprise features or your own cloud, E2B, Daytona and Northflank all advertise bring-your-own-cloud options. Quivvo costs more than InstaBox and boat, and offers a choice of region, no session limit, and prepaid credit that doesn't expire. Sources, read on 30 October 2026, with E2B and Daytona read again on 31 October and InstaBox and boat on 7 November: [InstaBox pricing](https://instabox.sh/docs/pricing.md), [boat pricing](https://docs.boat.dev/pricing), [Quivvo pricing](https://tryquivvo.com/pricing), [Northflank pricing](https://northflank.com/pricing), [Fly Sprites](https://fly.io/sprites), [E2B pricing](https://e2b.dev/pricing), [Daytona pricing](https://www.daytona.io/pricing). --- # Quivvo vs InstaBox Quivvo and InstaBox are built the same way: each agent gets a Firecracker microVM running Debian 13, with root, Docker on the default template, a persistent disk at `/data`, and a pause that keeps memory when it can. InstaBox costs less and does more. A 2 vCPU, 4 GiB machine costs about $0.017 per running hour on InstaBox and $0.054 on Quivvo, and InstaBox adds Android and CI templates and a URL for each port. Quivvo lets you choose the region a machine runs in, and its credit doesn't expire. ## How do they compare? | | Quivvo | InstaBox | | --- | --- | --- | | 2 vCPU + 4 GiB, per running hour | $0.054 | $0.017 | | Per vCPU-hour | $0.005 | $0.004 | | Per GiB-hour of memory | $0.011 | $0.00225 | | Disk | $0.20 per GiB-month, none included | 20 GiB on Free. Pro includes 200 GiB across machines, then $0.08 per GiB-month | | Free plan | $5 of credit once, 1 machine running at a time, up to 2 vCPUs and 4 GiB | 1 machine, 4 vCPUs and 8 GiB, up to 8 hours a run | | $20 a month plan | $20 of credit that doesn't expire, 25 machines at once | $20 of credit that resets monthly, 100 machines at once | | Largest machine | 8 vCPUs, 16 GiB | 32 vCPUs, 64 GiB on Team | | Region | Your choice of Amsterdam or Ashburn, Virginia | Chosen by InstaBox | | Public URLs | The machine's URL serves one published port | A URL for each port, up to 16, and a visit wakes a paused machine | | Desktop | On demand, with Chromium, in the dashboard or any VNC client | VNC desktop with Chromium | | Other templates | `universal`, `minimal` | Also Android 14 and a GitHub Actions runner | | Ways to drive it | CLI, REST API, hosted MCP server, TypeScript and Python SDKs | CLI, REST API, hosted MCP server, TypeScript and Python SDKs | InstaBox's figures come from [its pricing page](https://instabox.sh/docs/pricing.md), [limits](https://instabox.sh/docs/limits.md), [machine sizes](https://instabox.sh/docs/machines/sizes.md), [ports](https://instabox.sh/docs/ports.md) and [auto-pause](https://instabox.sh/docs/idle-suspend.md) pages, read on 7 November 2026. The hourly price is our arithmetic from its published rates. ## When should I choose InstaBox? Choose InstaBox if price matters most: a machine costs about a third as much per running hour. Choose it too if you want Android or CI machines, larger machines, or a public URL for each port that wakes the machine on the first visit. Its Free plan also allows a larger machine, 4 vCPUs and 8 GiB, and each of its paid plans runs more machines at once. ## When should I choose Quivvo? Choose Quivvo if you need machines in a particular region, such as close to your users or your data in North America or Europe, since InstaBox places machines itself. Choose it too if you'd rather buy credit that doesn't expire than credit that resets each month, or if your agents run long jobs on the Free plan, where Quivvo sets no limit on how long a machine runs. See the [full pricing comparison](https://tryquivvo.com/compare/sandbox-pricing). --- # Quivvo vs boat Quivvo and boat both give each agent a full Linux virtual machine with its own kernel, root, Docker and a disk that survives a stop. boat costs less: its 2 vCPU, 4 GB sandbox is $0.018 per running hour, against $0.054 on Quivvo. It also includes 51 GB of disk and nested virtualization. boat runs only in Europe and has no pause. Quivvo runs in Amsterdam and Ashburn, Virginia, suspends a machine with its memory when it can, and has a hosted MCP server. ## How do they compare? | | Quivvo | boat | | --- | --- | --- | | 2 vCPU + 4 GiB, per running hour | $0.054 | $0.018 | | 4 vCPU + 8 GiB, per running hour | $0.108 | $0.036 | | Disk | $0.20 per GiB-month, none included | 51 GB included on these sizes | | Starting out | $5 of credit, no card | 7-day trial with 25 compute hours, card required, then plans from $20 a month | | Idle machines | Suspend, keeping memory and processes when it can, otherwise stop | Stop only: processes end, the disk stays | | Regions | Amsterdam and Ashburn, Virginia | Germany, Finland and France | | Public URLs | The machine's URL serves one published port | A URL for each port, up to 50 | | Desktop | On demand, in the dashboard or any VNC client | Yes, streamed to the browser | | Nested virtualization | No | Yes, `/dev/kvm` in every sandbox | | Hosted MCP server for machines | Yes | Its docs describe an MCP server for reading the docs; agents drive sandboxes with the CLI | | Ways to drive it | CLI, REST API, hosted MCP server, TypeScript and Python SDKs | CLI, REST API, TypeScript and Python SDKs | boat's figures come from [its pricing](https://docs.boat.dev/pricing), [free trial](https://docs.boat.dev/free-trial.md), [hosting](https://docs.boat.dev/hosting.md), [security](https://docs.boat.dev/security.md) and [AI tools](https://docs.boat.dev/ai-tools.md) pages and [its site](https://boat.dev/), read on 7 November 2026. ## When should I choose boat? Choose boat if price matters most: a machine costs about a third as much per running hour, with disk included. Choose it too if your agents need to run virtual machines or Android emulators inside the sandbox, or need many public ports. Its $20 plan runs up to 100 sandboxes at once, against 25 on Quivvo's $20 Pro plan. ## When should I choose Quivvo? Choose Quivvo if your machines have to run outside Europe, close to users or data in North America. Choose it too if an idle machine should come back with its processes still running, since boat ends them at every stop. And choose it if your agents work through MCP, or if you want to try it without a card. See the [full pricing comparison](https://tryquivvo.com/compare/sandbox-pricing). --- # Quivvo vs E2B Both Quivvo and E2B give AI agents isolated cloud machines. The main differences are price and session length: a 2 vCPU, 4 GiB machine costs $0.054 per running hour on Quivvo and about $0.166 on E2B, and E2B's pricing page limits sessions to 1 hour on Hobby and 24 hours on Pro, while Quivvo machines have no session limit. E2B has been around longer, offers SDKs and has an enterprise tier. ## How do they compare? | | Quivvo | E2B | | --- | --- | --- | | 2 vCPU + 4 GiB, per running hour | $0.054 | $0.166 | | Per vCPU-hour | $0.005 | $0.0504 | | Per GiB-hour of memory | $0.011 | $0.0162 | | Disk | $0.20 per GiB-month | Included: 10 GiB on Hobby, 20 GiB on Pro | | Longest session | No limit; suspends when idle | 1 hour on Hobby, 24 hours on Pro | | Ways to drive it | CLI, REST API, hosted MCP server, TypeScript and Python SDKs | SDKs and API | | Bring your own cloud | No | Yes, on Enterprise | E2B's figures come from [its pricing page](https://e2b.dev/pricing), read on 31 October 2026. The hourly prices are our arithmetic from its per-second rates. ## When should I choose E2B? Choose E2B if you need a bring-your-own-cloud deployment, or want a provider with a long record: its site says more than 2 billion sandboxes have run on it. Quivvo doesn't offer bring-your-own-cloud. ## When should I choose Quivvo? Choose Quivvo if your agents do long-running work and you want a machine that keeps its state rather than a session that ends. A Quivvo machine has root, Docker on the default template and a persistent disk at `/data`. It suspends when idle, keeping memory and processes when it can and otherwise stopping, which keeps the disk, and wakes on the next command. It also costs about a third as much per running hour at list price, and its prepaid credit doesn't expire. See the [full pricing comparison](https://tryquivvo.com/compare/sandbox-pricing). --- # Quivvo vs Daytona Both Quivvo and Daytona run AI-generated code in isolated cloud environments. A 2 vCPU, 4 GiB machine costs $0.054 per running hour on Quivvo and about $0.166 on Daytona. Daytona's terms describe its sandboxes as a "container or microVM environment", while every Quivvo machine is a Firecracker microVM with its own kernel. Daytona offers SDKs in five languages and an enterprise tier. ## How do they compare? | | Quivvo | Daytona | | --- | --- | --- | | 2 vCPU + 4 GiB, per running hour | $0.054 | $0.166 | | Per vCPU-hour | $0.005 | $0.0504 | | Per GiB-hour of memory | $0.011 | $0.0162 | | Disk | $0.20 per GiB-month, none included | About $0.08 per GiB-month, first 5 GiB free | | Isolation | Firecracker microVM | "Container or microVM environment" | | Ways to drive it | CLI, REST API, hosted MCP server, SDKs for TypeScript and Python | SDKs for Python, TypeScript, Ruby, Go and Java | | Bring your own cloud | No | Yes, on Enterprise | Daytona's figures come from [its pricing page](https://www.daytona.io/pricing), read on 31 October 2026, and [its terms](https://www.daytona.io/terms-of-service) and [its site](https://www.daytona.io/), read on 30 October. The hourly prices are our arithmetic from its published rates. ## When should I choose Daytona? Choose Daytona if you want an SDK for Ruby, Go or Java, or need a bring-your-own-cloud deployment. Quivvo's SDKs are for TypeScript and Python, and it doesn't offer bring-your-own-cloud. Daytona also includes the first 5 GiB of disk, where Quivvo charges for all of it. ## When should I choose Quivvo? Choose Quivvo if you want a full VM for each agent at a lower hourly price. A Quivvo machine has root, Docker on the default template and a persistent disk at `/data`. It suspends when idle, keeping memory and processes when it can and otherwise stopping, which keeps the disk, and wakes on the next command. Agents can use it through a hosted MCP server, and one with no browser can sign itself up for you to approve. See the [full pricing comparison](https://tryquivvo.com/compare/sandbox-pricing). --- # Quivvo vs Fly Sprites Quivvo and Fly Sprites are both Linux computers for AI agents, and they bill differently. Sprites charge for the CPU and memory you actually use ($0.0385 per CPU-hour and $0.021875 per GB-hour of use), so a mostly idle machine can cost very little. Quivvo charges for what a machine is allocated while it runs: $0.054 an hour for 2 vCPUs and 4 GiB. Quivvo itself runs on Fly.io's infrastructure, on Fly Machines. ## How do they compare? | | Quivvo | Fly Sprites | | --- | --- | --- | | Billing | Allocated CPU and memory, per second while running | Actual CPU and memory use | | 2 vCPU + 4 GiB, busy for a full hour | $0.054 | Up to about $0.165 | | While suspended or asleep | Disk only | Storage only | | Disk | $0.20 per GiB-month | About $0.50 per GB-month awake, $0.02 asleep | | Saving state | Snapshots and forks of the disk; suspend keeps memory when it can | Whole-disk checkpoints | | Public URLs | Publish a port on the machine's URL | One HTTPS URL per Sprite, routed to port 8080 | | Hosted MCP server | Yes | Yes, a [remote MCP server](https://docs.fly.io/sprites/integrations/remote-mcp) | Sprites' figures come from [its site](https://fly.io/sprites) and [docs](https://docs.fly.io/sprites), read on 30 October 2026. ## When should I choose Fly Sprites? Choose Sprites if your agents sit idle most of the time and you want to pay only for actual use, or if whole-disk checkpoints are central to how you work. It also has the backing of Fly.io directly, where Quivvo is a layer on top of it. ## When should I choose Quivvo? Choose Quivvo if your machines are busy while they run, because a flat allocated rate then costs less: a machine busy for the full hour costs about a third as much. An agent with no browser can also sign itself up for you to approve. See the [full pricing comparison](https://tryquivvo.com/compare/sandbox-pricing). --- # Guides Practical guides to running AI agents on cloud machines. - [What is an AI agent sandbox?](https://tryquivvo.com/guides/what-is-an-ai-agent-sandbox) - [How to give Claude Code a cloud machine](https://tryquivvo.com/guides/cloud-machine-for-claude-code) --- # What is an AI agent sandbox? An AI agent sandbox is an isolated computing environment where an AI agent can run code, install software and change files without being able to touch your own machine, your data or other customers' workloads. Because agents write and run code nobody has reviewed, the sandbox limits the damage when that code is wrong or malicious. Most are cloud services that start an environment through an API. ## Why do AI agents need a sandbox? An agent that only answers questions needs no computer. An agent that does work does: it clones repositories, installs packages, runs tests and starts servers. Running that on your laptop or production servers gives it your credentials and your files, and a prompt injection in a web page or a dependency can turn the agent against you. A sandbox keeps that code away from anything you care about. ## What are the kinds of isolation? | Kind | How it isolates | Trade-off | | --- | --- | --- | | Container | Shares the host's kernel, separated by namespaces and cgroups | Light, but a kernel exploit can reach the host | | gVisor | Intercepts system calls in a user-space kernel | Stronger than a container, with some compatibility and speed cost | | MicroVM (such as Firecracker) | Runs its own kernel on a minimal virtual machine | Strongest of the three, and can run Docker inside; heavier than a container | For code nobody has reviewed, a microVM is the safer default, since each agent gets its own kernel rather than sharing one. ## What should I look for? - **Isolation:** does each agent get its own kernel? - **Persistence:** do files survive, and for how long? Some sandboxes are throwaway; others keep a disk. - **Pause and resume:** can it suspend and keep memory and processes, so an idle agent stops costing CPU? Does it tell you when it couldn't? - **Session limits:** is there a maximum run time? Long-running agents hit these first. - **Root and Docker:** can the agent install anything and run containers? - **Network:** can you publish a port as a URL, and control what it can reach? - **How agents reach it:** a CLI, an API, SDKs, or an MCP server for coding agents. - **Billing model:** allocated resources or actual use, and what idle time costs. See the [pricing comparison](https://tryquivvo.com/compare/sandbox-pricing). ## Do I need a sandbox or a whole VM? "Sandbox" often means a short-lived environment for one piece of code, such as a data analysis snippet. If your agent works for hours, keeps state between tasks or runs servers, you want something closer to a full computer that persists. [Quivvo](https://tryquivvo.com/) is that kind of product: each machine is a Firecracker microVM with root and a persistent disk, Docker on the default template, which suspends when idle and wakes on the next command. --- # How to give Claude Code a cloud machine To give Claude Code a cloud machine, sign in to Quivvo and add its MCP server with `quivvo mcp init claude`. Claude Code then has tools to create Linux machines, run commands, read and write files and publish ports, so it can do risky or long-running work away from your laptop. The same steps work for Cursor and Codex. If you'd rather let the agent do this, tell it to follow [the setup runbook](https://tryquivvo.com/setup.md). ## Step 1: Install and sign in ```bash command -v quivvo >/dev/null || npm i -g quivvo@latest quivvo login ``` This opens your browser to approve the sign-in and saves a key in `~/.config/quivvo/credentials.json`. New accounts get $5 of credit. New accounts wait in a queue for now, and the credit arrives with their turn. Each person who signs up with your invite link moves you up 10 places, and a paid plan skips it. ## Step 2: Add the MCP server ```bash quivvo mcp init claude ``` That registers the [hosted MCP server](https://tryquivvo.com/docs/mcp) with Claude Code. The config refers to `$QUIVVO_API_KEY` rather than holding your key, so set that variable where you start Claude Code; the command tells you how. For other clients, use `quivvo mcp init cursor` or `quivvo mcp init codex`. ## Step 3: Ask Claude Code to use it Start Claude Code and ask for something like: > Create a Quivvo machine called api-test, clone https://github.com/acme/api into it, install the dependencies, run the test suite, and tell me what fails. Delete the machine when you're done. Claude Code calls `create_machine`, then `exec` for each command, and `delete_machine` at the end. A command that's still running when `exec` answers keeps going, and it follows up with `job_status` and `job_logs`. ## Can Claude Code run inside the machine instead? Yes. The default template has Claude Code and Codex installed, so you can open a shell and run them there, or hand one a task and close your laptop. See [handing a task to Claude Code or Codex](https://tryquivvo.com/docs/recipes#hand-a-task-to-claude-code-or-codex). ## How do I watch what it's doing? Open a shell in the same machine with `quivvo ssh api-test`, or open it in the dashboard, which has a terminal in the browser. Every command also shows up in `quivvo history api-test`, and `quivvo output` shows what it printed. ## How do I keep costs down? Machines suspend themselves after 30 idle minutes by default, or 15 on the Free plan, and then only pay for disk. A 2 vCPU, 4 GiB machine costs $0.054 per running hour. Ask the agent to delete machines it no longer needs, or to create them with `delete_after_minutes` so they go whatever happens, and check spending with `quivvo usage`. ## Can the agent sign up by itself? Yes. If it's running somewhere with no browser, it can run `quivvo login --agent --no-wait --json` and give you a code to approve. See [agent sign-up](https://tryquivvo.com/docs/agents).