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.
{
"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.
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 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.
Give your agent a computer
npx quivvo@latest sh