---
title: "Cool Computers for agents"
description: "What an agent needs to work with Cool Computers for a user after setup: publish and update sites, give one a VM, share it, connect services, and handle errors."
canonical: "https://www.cool.computer/agents"
last_updated: "2026-11-06"
---
# Cool Computers for agents

> What an agent needs to work with Cool Computers for a user after setup: publish and update sites, give one a VM, share it, connect services, and handle errors.

You are an agent working with Cool Computers for a user: computers at `name.cool.computer` with a public address, an inbox, and a disk that stays. If the user asked you to set up or try Cool Computers, or to put something online before they have an account, follow https://www.cool.computer/setup.md first: it installs `cool`, puts their first page online, signs them up and shows them comments on the page. This page is everything after that. Do not use the website's sign-in form; it is for humans in a browser.

## Before you start

Use the `cool` CLI (`--json` prints JSON); without a shell, use the HTTP calls under each section, at `https://api.cool.computer`. Make a scratch directory for responses and check who is signed in:

```sh
WORK="$(mktemp -d)"
cool whoami --json
```

If `cool` is missing, install it as https://www.cool.computer/setup.md shows. When `cool whoami` prints the `email` of the user you are working for, you are signed in: set `TOKEN="$COOL_API_KEY"` if that variable exists, for the HTTP calls below. When it prints someone else's email, stop and ask the user. When it answers `401` while `COOL_API_KEY` is set, that key is dead: `unset COOL_API_KEY` (sign-in refuses to run while one is set) and run `cool whoami --json` again, since a saved sign-in may answer. Still `401`, sign in again with an email code ([/authentication.md](/authentication.md#authenticate-with-email)) and resume the task; a user with no account yet is set up through https://www.cool.computer/setup.md. Never ask the user to paste an API key into the conversation; if they have one, ask them to put it in `COOL_API_KEY` and resume.

The examples use the user's site `alex-heath`; set `ID` to a computer's `id` (from `cool list --json`, under `computers`) where an HTTP call needs it.

## When code needs a connected service

If the task needs Claude, Codex, Grok, Pi, or GitHub, inspect `cool integrations`,
then run the matching command: `cool integrations connect claude`,
`cool integrations connect codex`, `cool integrations connect grok`,
or `cool integrations connect github`.
Claude, Codex and Grok sign in on a temporary VM, so they need Pro: on Free, `connect` answers `402 pro_required`; tell the user (`cool billing upgrade`, or `cool billing portal` for a lapsed subscription). GitHub works on Free.
Pi uses your Claude and Codex connections. Automatic Codex selection requires the current image and a Codex connection; on older computers, choose Codex with `pi --model openai-codex/gpt-5` or in Pi's settings. Hand the approval link or
code to the human and wait for them to approve; never approve it on their behalf.

Inside an unshared computer, run `cool integrations` and use the token
exactly as its example shows, keeping wrappers such as `sk-ant-oat01-` and the
Codex JWT form; the proxy swaps in the real credential on matching requests. Git and gh work automatically when GitHub is
connected. Real credentials stay on Cool Computers' servers. Shared computers
report integrations withheld; use an unshared computer for these services.

## Publish a site

Publishing changes a site that has no VM: it is instant and needs none. Running something needs a VM: give the site one with `cool vm` (or `POST /api/computers/$ID/vm`, or MCP `cool_give_vm`), or start its program with `cool service run`, which gives it one; what changes then is under "When a site gets a VM" below. `cool exec` (MCP `cool_exec`), `cool enter` and `cool ssh` on a site without a VM answer `409 needs_vm` and change nothing. For a new computer to run things on, `cool create` makes one with its VM at once. Read files with `cool files`, the HTTP files API, or MCP `cool_list_files` and `cool_read_file` instead of running a command to look at them; none of them needs a VM. Over MCP, `cool_exec` runs one command and `cool_write_files` writes files on a computer that has a VM.

When the user wants a website, page, browser app or artifact online, or asks you to build them a "cool" one, publish its files: they are served at the site's own address, with no VM running behind them. Build framework apps first (for example `npm run build`), on your machine or any computer, and publish the build output; publishing never runs a build.

```sh
cool publish ./dist --name alex-heath-site --json
```

That creates a new computer serving `./dist` at `https://alex-heath-site.cool.computer`. The output gives `url` and `release_url`, this release's own address, such as `https://r1--alex-heath-site.cool.computer`. To update it, run `cool publish ./dist --computer alex-heath-site --json`; only changed files are uploaded. One file works too: `cool publish welcome.html` publishes it as `index.html`.

A new site is public unless it is created private. Signed in, create it private unless the user wants it public: `cool publish ./dist --name alex-heath-site --visibility private --json` (MCP `visibility: "private"`), so only the user's account and the people they share it with can open it. `cool share public alex-heath-site` (MCP `cool_set_computer_visibility`) makes it public later. After a publish, set `URL` to the `url` it printed and tell the user; if you run on their machine, then run `open "$URL"` (macOS) or `xdg-open "$URL"` (Linux).

Over HTTP, send the directory as one gzip tar with an idempotency key:

```sh
tar -czf "$WORK/site.tar.gz" -C dist .
SITE_KEY="site-alex-heath-site-$(date +%s)"
curl -sS -o "$WORK/site.json" -w '%{http_code}\n' "https://api.cool.computer/api/publish?name=alex-heath-site" \
  -H "authorization: Bearer $TOKEN" -H "idempotency-key: $SITE_KEY" \
  -H 'content-type: application/gzip' --data-binary "@$WORK/site.tar.gz"
```

Use `?computer=alex-heath-site` instead of `?name=` to publish to an existing site. Add `&visibility=private` next to `?name=` to create the site private, so only the user's account and the people they share it with can open it. On a `5xx` or no response, rerun only the `curl` line with the same `SITE_KEY`. With MCP, call `cool_publish` with `files`, each a `path` with text `content` or `content_base64` (add `"executable": true` for a script the server runs), and `name` (with `visibility` set to `private` for a private site) or `computer`, a site's name or id; its arguments are limited to 64 KiB in total, so publish anything larger with `cool publish` or `POST /api/publish`.

Without an account, follow https://www.cool.computer/setup.md from step 1: it publishes what the user asked for, then signs them up and keeps it. Until the site is kept, publish to it again with `cool publish ./dist --claim "$CLAIM_URL"` (HTTP: `?computer=$ID` plus `authorization: Claim $CLAIM_KEY`).

A `cool.json` at the root of the files configures the site:

- `{"spa": true}` serves `index.html` for paths that are not files, for apps that route in the browser.
- `{"run": "node server.js", "port": 3000}` is a shortcut for starting the app's server by hand (`port` defaults to 3000): publishing it to a site with no VM gives the site its own VM, writes the files to `/home/runtime/app`, where the site serves them at once, then runs the command there as the service, which, once it is ready, answers every path, `/api` and the site's files included (a `run` release declares no functions). The publish waits up to 60 seconds for the program's port to accept connections; a program still starting then is no failure, and the publish succeeds with a `Note:` that it keeps starting. A program that fails to start or ends before it is ready fails the publish with `502 server_start_failed`, but the release stays live and the site keeps its VM, also when the publish created it (unless the link to its VM did not move either; the error then says the created computer was deleted, so publish again): read why with `cool service logs alex-heath-site`, fix the program with `cool files` or `cool exec`, and start it again with `cool service run`. The site keeps serving its files while the program starts and if it fails; once the program is ready the folder is no longer served as files, so the program serves its own static files. Keep keys out of the folder anyway: put them in a secret set (`cool secrets set`). `node_modules/` is never published, so install dependencies in the command: `"run": "npm ci && node server.js"`. Files keep their execute bit, so `run` can be `./start.sh`. The site then has a VM; see "When a site gets a VM" below.
- `{"functions": "api/worker.js"}` names one bundled ES module (`.js` or `.mjs`, at most 1 MiB; bundle its imports first, e.g. `esbuild api.js --bundle --format=esm --outfile=api/worker.js`) whose `export default { fetch(request, env) }` answers `/api` and every request under `/api/` without a VM; every other path stays a file, and the module itself is not served. Use only Web-standard APIs (`fetch`, `Request`, `Response`, `crypto`); each request gets 50 ms of CPU and 20 outgoing requests. Only the site's own URL gives the functions their secret set and `env.DB`; a release URL such as `r2--mysite.cool.computer` runs them without either, the live release's too, so test secrets and data on the site's URL. Add `"database": true` alongside `"functions"` to give the live release's edge functions a D1 database through `env.DB` (reserved as a secret key):

  ```js
  const rows = await env.DB.prepare("SELECT title FROM posts WHERE id = ?").bind(1).all();
  return Response.json(rows.results);
  ```
  Every `env.DB` call returns a promise: `await` each one, or the function fails with a `TypeError`. Inspect or seed the site's database with `cool db query alex-heath-site "SELECT * FROM posts LIMIT 10"` (use `--file schema.sql` to run a SQL file).
  When a request under `/api/` answers `function failed`, read why with `cool logs alex-heath-site`: the latest failures, newest first, each with the request's method and path, the release and the exception's name, message and first stack frame (the MCP tool is `cool_get_logs`; HTTP `GET /api/computers/$ID/logs`). Only an invocation that threw or passed its limit is kept; a function that answers an error status itself, and `console.log` output, are not. A private site shows its owner the same text in the failing response itself. A site with a VM runs its functions there; its failures are in the VM's log: `cool exec alex-heath-site -- journalctl -u runtime-frontdoor -n 100 --no-pager`.
- On the site's own URL the functions can send email from the site's address, such as `alex-heath@mail.cool.computer`, with `env.EMAIL.send({ to, subject, text })` (or `html`), so a contact form needs no VM and no form service. Each send counts as the computer's own `cool email send`: the same limits and billing, and it is stored in the computer's inbox. Add `idempotencyKey` to send at most once when retrying; a refused send throws an `Error` that says what to do next. A release URL runs the functions without `EMAIL`, and `EMAIL` is reserved as a secret key. For example, `api/worker.js` emailing the owner what a form posts to `/api/contact`:

  ```js
  export default {
    async fetch(request, env) {
      const form = await request.formData();
      await env.EMAIL.send({
        to: "you@example.com",
        subject: `Message from ${form.get("name")}`,
        text: `${form.get("email")} wrote:\n\n${form.get("message")}`,
      });
      return Response.redirect(new URL("/thanks.html", request.url), 303);
    },
  };
  ```
- `{"functions": "api/worker.js", "secrets": "site-keys"}` gives the functions the keys of an account secret set as `env`: create it with `cool secrets set site-keys STRIPE_KEY=sk_... OPENAI_KEY=sk-... --proxy OPENAI_KEY=api.openai.com` (or `PUT /api/secrets/site-keys`, or MCP `cool_set_secrets` with `secret_set`, `values` and `proxy`, which likewise replaces the whole set). A key set with `--proxy` reaches functions once the site has its own VM, and then only as a per-key stand-in: send the supplied `env.OPENAI_KEY` as the Bearer token in the `Authorization` header and the computer's egress proxy substitutes the real value on matching `https` requests to that host, so the function never holds it; before that the function gets only the plain keys. Changing or deleting the set reaches published functions within seconds of the command returning (on the edge a replaced script can take up to a minute to serve everywhere); a release naming a set that does not exist fails (`422 secret_set_not_found`). A private site checks the visitor before any function runs.
- `functions` needs an account and cannot be combined with `run` (`422 functions_and_run`).

**Editing a document someone may have open.** A document artifact saves the edits of its owner and share members from their browser, so read its current files with `cool files` first: the live file may be newer than the one you published. Your publish reaches a person who has it open within about 20 seconds while the tab is visible: their page reloads to it, or, when they have unsaved edits, their next save is a conflict and they choose between your version and a downloaded copy of theirs. Keep the viewer and `artifact.json` when you change the content. Once the site has a VM, browser saves stop.

**Building a document viewer.** Declare the document in `artifact.json` at the site root: `{"kind": "docx", "file": "document/report.docx"}` (`kind` is `docx`, `xlsx`, `pptx` or `pdf`, with `file` under `document/`), or `{"kind": "slides", "file": "deck/deck.json"}`. A save may write only that file, and for slides also slide HTML such as `deck/slides/welcome.html` and `png`, `jpg`, `jpeg`, `gif`, `webp` or `svg` images under `deck/`; each file is at most 25 MiB, a save at most 64 MiB. The page talks to the platform with `window.postMessage({cool: "doc", type, ...}, location.origin)` on its own window and listens for answers there:

- Send `hello` on load, again after 3 seconds without an answer. `access` answers `canSave`, `version` and, when `canSave` is false, `reason` (`signed_out`, `read_only` or `not_document`). Edit only after `canSave`.
- Read every file the document needs when the page loads, from the release that served it: `fetch("/__cool/document/" + access.version + "/document/report.docx")`. An older release can disappear later. Once the site has a VM those reads move to the file's own path, and a page loaded from the VM gets no answer to `hello`: show the document read-only, read from the files' own paths.
- Send `save` with a new unique `id`, `base` (the version the edits were made on) and `files: [{path, bytes}]` (ArrayBuffers, transferred), about two seconds after an edit and when the page is hidden, one at a time; guard leaving with `beforeunload` while edits are unsaved. `saved` answers the new `version`, the next save's base.
- `save_failed` has `id` and `reason`: `conflict` (someone published `version`: offer "Reload their version" and "Download a copy"), `offline` (save again with the same id, after `retryAfter` seconds when given), `signed_out` (ask to sign in again), `forbidden` (editing is no longer allowed), `read_only` (the site has a VM and takes no saves), `path_not_allowed` (the page saved a file outside the declaration), `not_document` ("This page is no longer a document"), `too_large` or `error` (keep the edits and let the person try again).
- `changed` with `version` says the live document moved: reload the page and its files when there are no unsaved edits. There is no Save button.

### When a site gets a VM

A published site gets its own VM only when you ask: `cool vm alex-heath` (`POST /api/computers/$ID/vm`), `cool service run`, or a publish whose `cool.json` declares `run`. VMs need Pro; on Free these answer `402 pro_required`. It is one-way: the VM is 1 of the account's 20 and takes memory from the account's 8 GiB, which all its VMs share (one VM can use most of it while the others are idle (each idle one keeps about 110 MiB); a VM that does not fit is refused with `account_memory_full`), and publishing to the site stops. `cool vm` waits until the VM is ready, and running it again on a computer that already has one changes nothing, except that it finishes moving the site's link to the VM when an earlier attempt left it on the published site; while that still fails it answers `502 artifact_deploy_failed`, the VM is kept, and running it again in a moment retries. Before it returns, the site moves to the VM by itself: the published files are in `/home/runtime/app`, the computer's built-in site server serves them exactly as before, functions, secrets and `env.EMAIL` included (the module is kept out of the served folder, under `/home/runtime/.cool/functions`), and the URL never stops answering. From then on `/home/runtime/app` is the site: edit a file there with `cool files` or inside `cool enter` and it is live at once; hidden files such as `.env`, `node_modules/` and `cool.json` are never served. Once a port is exposed (`cool service run --port`, a `run` publish, or `cool expose`), the program answers every path, except `/api` on a site that declares functions, and the folder is never served as files again, even after the port is removed, when the URL shows a "Nothing here yet" page: to serve files again, run a static file server as the service. From then on, change it with `cool files` and `cool exec`; `cool.json` no longer applies, and publishing to it is refused (`409 computer_has_vm`), so publish to a new name for a separate site. The command that gives the site its VM says so: the CLI prints a `Note:` line on stderr (with `--json`, the result's `notices`), the HTTP answer carries a `Cool-Notice` header, and an MCP result an extra line. Without a VM, `cool files` reads the published files and writing or deleting one publishes a new release.

If the live site uses `"database": true`, its database moves with it to `/home/runtime/.cool/database/`. While the database is copied, function requests answer `503` with `Retry-After` for as long as the copy takes, longer for a larger database; static files keep serving. After the move, `env.DB` uses the local data. Integers beyond ±9,007,199,254,740,991 (2^53−1) change during the move, because Cloudflare's D1 export rounds them; store such values as TEXT.

## Private page, then share it

When the user wants a page only they and a colleague can open, publish it private from the start, so it is never public, then share it with the colleague's email. There is no share link: ask the user for the colleague's email address and set `COLLEAGUE` to exactly what they gave you.

```sh
COLLEAGUE="the-colleagues-address@example.com"
cool publish notes.html --name alex-heath-notes --visibility private --json
cool share add alex-heath-notes "$COLLEAGUE" --json
```

Over HTTP, publish signed in, set `ID` from `computer.id` in its response, then share:

```sh
NOTES_KEY="publish-alex-heath-notes-$(date +%s)"
curl -sS -o "$WORK/notes.json" -w '%{http_code}\n' "https://api.cool.computer/api/publish?name=alex-heath-notes&visibility=private" \
  -H "authorization: Bearer $TOKEN" -H "idempotency-key: $NOTES_KEY" \
  -H 'content-type: text/html' --data-binary "@notes.html"
ID="$(jq -er .computer.id "$WORK/notes.json")"
curl -sS -o "$WORK/share.json" -w '%{http_code}\n' "https://api.cool.computer/api/computers/$ID/shares" \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' --data "{\"email\":\"$COLLEAGUE\"}"
```

A signed-out visitor to a private site is sent to sign in. The colleague gets an email invitation, signs in with that address, and can open `https://alex-heath-notes.cool.computer`. With MCP, publish it with `cool_publish` and `visibility` set to `private`, then call `cool_share` with `computer`, the colleague's `email` and `access` set to `can_use`; `access` set to `none` stops sharing it, and the same call twice sends one invitation. Sharing gives the colleague full access to the computer: anything already on it, and the secret set its `cool.json` names for its functions, are theirs to read too. While it is shared it cannot get any other secret set (`computer_shared`) or account integrations; keep those on a computer that is not shared. A shared computer withholds all the owner's connected services (Claude, Codex, Grok, and GitHub). Proxy access granted before sharing, including secret sets and integrations, is withheld too, except the site's own function rules. Plaintext secrets already on the computer remain there. Unsharing lets the computer use those credentials again, including any program the person left running on it, so if the user no longer trusts them, keep the user's credentials on a fresh computer instead.

## Then keep going

- Run commands on a computer with a VM: give the welcome site one with `cool vm alex-heath` (`POST /api/computers/$ID/vm`), then `cool exec alex-heath -- uname -a`. On a site without a VM, exec answers `409 needs_vm` and runs nothing.
- Over HTTP, `POST /api/computers/$ID/exec` with `{"command":"uname -a"}` waits for the result, and the server mints the `execution_id` when you send none. Send your own (the computer's `ID`, `.exec.`, and 32 new hex digits) when a lost response must not run the command twice: send the same `execution_id` to `/exec/attach` instead of running it again:

```sh
EXEC_ID="$ID.exec.$(openssl rand -hex 16)"
curl -sS -o "$WORK/exec.json" -w '%{http_code}\n' "https://api.cool.computer/api/computers/$ID/exec" \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  --data "{\"execution_id\":\"$EXEC_ID\",\"command\":\"uname -a\"}"
jq -r '.stdout, .stderr, .exit_code' "$WORK/exec.json"
```

- Read and write files with `cool files`; copy whole directories with `cool files upload` and `cool files download`. A relative path is relative to `/home/runtime`.
- Change the public service with `cool service run`; check it with `cool service show` and `cool service logs`. Unlike `cool exec`, `cool service run` quotes every word after `--`: `cool service run bakery --port 3000 -- node server.js` works, but one quoted shell line such as `-- 'cd app && node server.js'` is passed as a single program name and fails. Write a shell line as `-- bash -c 'cd app && node server.js'`.
- A computer has one service: the one program its URL serves, or one worker. For another long-running program, start a systemd unit on the VM: `cool exec alex-heath -- "sudo systemd-run --unit=worker --uid=runtime --working-directory=/home/runtime/app --property=Restart=always node worker.js"`. Read its logs with `sudo journalctl -u worker` and stop it with `sudo systemctl stop worker`; reach a port it listens on privately with `cool proxy start alex-heath 9000`. The platform does not watch these units: `cool service` neither shows nor restarts them (systemd's `Restart=always` does), and a unit started this way is gone if the VM restarts.
- Reach a database or other TCP port on a VM from the user's machine with `cool proxy start bakery 5432`, then connect to `127.0.0.1:5432` (for Postgres, `psql -h 127.0.0.1 -p 5432`); the program must listen on `127.0.0.1` or all addresses inside the VM, and `cool proxy stop --all` ends it. The proxy runs on the user's machine, not in the account shell, and a site without a VM answers `needs_vm`.
- Serve a computer on the user's own subdomain, such as `app.example.com`: `cool domain set alex-heath app.example.com` prints the two DNS records the user creates at their DNS provider (a `TXT` that proves they own the name and a `CNAME` to `fallback--domains.cool.computer`), and `cool domain show alex-heath` shows its state and what it waits for until it is `active`, usually a few minutes after both records resolve, and why an `active` domain is not serving if it ever is not. `cool domain remove alex-heath` removes it. Without a shell, `PUT /api/computers/$ID/domain` with `{"hostname":"app.example.com"}`, `GET` and `DELETE` on the same path do the same. One domain per computer; its own `cool.computer` address keeps working, and is where page comments show: they are not on custom domains yet.
- Run something on a schedule with a systemd timer; there is no cron, so do not install one. `cool exec alex-heath -- sudo systemd-run --uid=runtime --working-directory=/home/runtime --unit=report --on-calendar='*:0/15' /home/runtime/report.sh` runs `report.sh` every 15 minutes, and `systemctl list-timers` shows it. That timer ends if the computer restarts; `cool help reference` shows how to keep one.
- Take the comments the user sends you from a page: after publishing a site with no VM, such as the private page above, run `cool comments watch alex-heath-notes --agent "Claude Code"` in the background, once per site, with your own name after `--agent` (how to run it in each agent: see `cool skill collaborate`); when it prints a line, run its `next`, do the comments, answer each with `cool comments resolve`, then run the watch again.
- For everything else read [/docs.md](/docs.md) and [/openapi.json](/openapi.json).

Ask the user before you delete a computer, send email from it, install a durable command that runs on its own, or do anything that costs money. Inspect before you change anything.

## Send feedback

When something in Cool Computers broke, confused you, or worked well, tell the team. No sign-in is needed; signed in, the feedback names the user's account. Keep the user's private data out of the message.

```sh
cool feedback "publishing welcome.html worked on the first try"
```

Without a shell, send it over HTTP; add `-H "authorization: Bearer $TOKEN"` to attach the user's account:

```sh
curl -sS -w '%{http_code}\n' https://api.cool.computer/api/feedback \
  -H 'content-type: application/json' --data '{"message":"publishing welcome.html worked on the first try","source":"agent"}'
```

A `201` means it was received. A `429 feedback_rate_limited` means the hourly limit was reached: wait `Retry-After` seconds, or send it later.

## Errors

- `401`: missing, expired, or revoked credential. If you came through `/auth.md`, re-exchange your assertion there; otherwise sign in again with an email code ([/authentication.md](/authentication.md#authenticate-with-email)) and resume the task. If an API key stopped working, it was revoked; delete your copy.
- `403 access_token_required`: API keys cannot manage API keys; send the access token from email sign-in (`cool login`, or [/authentication.md](/authentication.md#authenticate-with-email)) instead, and keep it.
- `403 interactive_required` while completing sign-in: the account needs the website; give the user https://www.cool.computer and wait.
- `403 sign_up_waitlisted` while completing sign-up: Cool Computers is full right now and the user's email has no invitation, so it joined the waitlist instead of getting an account. Tell the user, and stop: they get an invitation by email when they are let in, and signing up again with that email works then, or later once there is room. Do not retry in a loop.
- `409 claim_unavailable` while keeping a site: it expired, or another account kept it; `404` once an expired site is deleted. Publish its files again signed in.
- `402 pro_required`: VMs need Pro, and the account is on Free. Tell the user they can start Pro with `cool billing upgrade`, or fix a lapsed subscription (past due, unpaid or paused) with `cool billing portal`, or use the Account page; then try again (`cool billing` says which); see [pricing](/pricing).
- `403 site_limit_reached` while publishing a new site or keeping one: Free holds 20 sites at once. Tell the user to delete one or upgrade to Pro, then try again.
- `409` on publish: read the problem `code`. `name_taken`: another account owns the name; send one of the free names in `suggestions` instead (the first has a random suffix, so concurrent agents do not collide). `name_is_yours`: the user already has a computer with that name, and `context` says whether it has a `vm` and the `visibility` it keeps. Unless the user asked you to change that computer, stop and ask them first, since it may hold their work; with their go, publish to a site with `?computer=` and that name (`cool publish --computer` with it), write a VM's files with the files API instead, change the visibility first if it differs from what the user wants, or choose another name. `name_releasing`: one of the user's own computers still holds that name while it is being created or deleted; wait `Retry-After` seconds and rerun the same request (the CLI does this itself). `idempotency_in_progress`: wait `Retry-After` seconds and rerun the identical request. `idempotency_outcome_unknown`: do not rerun; check `cool list --json` and continue from what exists. `publish_in_progress`: another publish of that site is running; wait and retry.
- `429`: rate limited. Wait for `Retry-After`. A signed-out publish refused with `unclaimed_computer_limit`, `anonymous_publish_capacity` or `anonymous_publish_rate_exceeded` has no wait: sign the user up (step 4 of https://www.cool.computer/setup.md), then publish signed in. Never loop on `cool signup`.
- `409 needs_vm`: the computer is a site without a VM, and the command needs one. When the task needs to run something there, run `cool vm alex-heath` (`POST /api/computers/$ID/vm`), which is one-way and stops publishing to it, then retry; to only look at its files, use `cool files` instead.
- `503 host_capacity_full` while creating a computer or giving a site its VM: Cool Computers is at capacity right now. A site remains available; wait `Retry-After` seconds (about a minute) and retry once. If it is still full, tell the user and stop.
- `5xx` or no response: temporary failure. For HTTP requests, back off and rerun the same request, reusing its `PUBLISH_KEY` or `SITE_KEY` when it has one; never regenerate an idempotency key for a retry. Never rerun a database query (`cool db query`, `POST /api/computers/$ID/database/query`) blindly: it has no idempotency key, so a write may have committed; read the data first and rerun only what is missing. Never rerun `POST /api/auth/email/start` on your own: each call sends another code, so ask the user whether one arrived before requesting a new one. `cool create` retries a lost response itself under one idempotency key, so it never makes a second computer; if it still reports that the computer may have been created, check `cool list --json` before creating another. After a lost `cool service run` response, which sends no idempotency key, do not rerun it: check `cool service show`, and continue from what already exists. API-key creation is never retried: a key may already exist, so ask the user to check `cool api-keys list` first.

## Reference

- [/auth.md](/auth.md): the standards-based WorkOS agent registration, for agents arriving from a `401` challenge.
- [/authentication.md](/authentication.md): every credential type and how they differ.
- [/llms.txt](/llms.txt): the index of everything here.
- [https://cool.computer/docs](https://cool.computer/docs): the full guide.
