# Mandala Computer > Full cloud computers your agents can see, control, and operate. Create a machine, watch its screen, > click and type on it, run commands inside it, and snapshot it. ## SDKs Three clients wrap this API; each reads the key from `MANDALA_API_KEY`. - Python: `pip install mandala-computer` — https://github.com/mandalacomputer/python-sdk - TypeScript: `npm install mandala-computer` — https://github.com/mandalacomputer/typescript-sdk - MCP server, every endpoint as a tool: `npx -y mandala-computer-mcp` — https://github.com/mandalacomputer/mcp In Claude Code, install it as a plugin instead: `/plugin marketplace add mandalacomputer/mcp` then `/plugin install mandala-computer@mandala`. That brings a skill along with the server — when to reach for a computer, that it costs money until suspended or stopped, and which refusals are worth a retry. ## Authentication The REST resource operations documented here carry an API key as a bearer token; OPTIONS is public path metadata. Keys are created in Settings or through explicit browser approval and are shown once. Human device login uses separate authentication bootstrap endpoints outside this bearer resource API: see https://app.mandala.computer/docs/device-login for the JSON protocol. ``` curl -H "Authorization: Bearer com_..." {ORIGIN}/api/v1/computers ``` A key acts as one account. A key issued against a workspace is confined to it and sees only the computers in that workspace, which is what makes a leaked CI key a bounded problem. HTTP conventions: every public GET route also accepts HEAD with no response body. HEAD retains the GET role and scope checks. Ordinary finite reads compute GET metadata; file and legacy exec-PID HEAD use native metadata checks, never resume a guest or consume output. Build-event HEAD opens no stream or held stream slot. Artifact/output HEAD verifies retained metadata and file safety without reading chunks. Body-derived headers or failures detectable only by reading content may be unavailable. Range is ignored on HEAD. OPTIONS is public, bodyless path metadata. It returns only that public path’s Allow methods, including HEAD for GET; unknown, unsafe and daemon-only paths remain 404. An authenticated unsupported method on a known public path returns 405 with the same path-specific Allow, after authority and rate checks. For /computers: GET, HEAD, POST, OPTIONS. Application responses carry a server-generated X-Request-ID. Finite HTTP error objects and application SSE error frames also carry request_id; OpenAI errors retain their nested error object and streaming [DONE] terminator. Caller X-Request-ID is not accepted as the correlation identity. Successful binary/SSE content is unchanged. HEAD errors carry correlation only in the header. Non-JSON, malformed, aborted, oversized (over 1 MiB), or stalled (over 2 seconds of error-body reading) upstream errors use a small correlated envelope with the original status. Infrastructure failures outside the application may have a different shape. Platform API-key 401 reasons are missing (no Authorization attempt), invalid (malformed or unknown credential), and revoked (retained evidence of revocation). WWW-Authenticate is Bearer for missing and Bearer error="invalid_token" for invalid/revoked. Revocation evidence contains hashes only, retained for at most 90 days and the newest 100,000 hashes globally; older, pre-upgrade or evicted evidence means invalid. A fresh revoked request cannot recover the former holder’s identity. Known suspension or lost membership remains an authority 403; provider 401 errors do not mean the platform API key is invalid. The computer’s events_url is a WebSocket capability: use the exact returned URL with its embedded controlling desktop credential. Do not replace its origin with the REST base URL. A Bearer API key alone receives uniform JSON 400 guidance naming events_url; it does not authenticate the socket. Base URL: `https://app.mandala.computer/api/v1` OpenAPI: `https://app.mandala.computer/api/docs/openapi.json` Reference: `https://app.mandala.computer/docs` Full reference, one file: `https://app.mandala.computer/llms-full.txt` ## Endpoints ### Account Effective plan ceilings and advisory remaining quota. - `GET /account` — Read account quota (role: viewer) ### Templates What a computer can be built from, and the sizes it can be built at. - `GET /templates` — List templates (role: viewer) - `GET /templates/schema` — Get the template schema (role: viewer) - `POST /templates/validate` — Check a template document (role: member) - `POST /templates` — Publish a template (role: member) - `GET /templates/{namespace}/{name}` — Get a template (role: member) - `DELETE /templates/{namespace}/{name}` — Retire a template (role: member) - `GET /sizes` — List sizes (role: viewer) ### Builds Compiling a template document into an image of your own. - `POST /builds` — Build a template (role: member) - `GET /builds` — List builds (role: viewer) - `GET /builds/{id}` — Get a build (role: viewer) - `GET /builds/{id}/progress` — Get build progress (role: viewer) - `GET /builds/{id}/events` — Stream build progress (role: viewer) ### Computers Creating machines, and their lifecycle. - `GET /computers` — List computers (role: viewer) - `POST /computers` — Create a computer (role: member) - `GET /computers/{id}` — Get a computer (role: viewer) - `PATCH /computers/{id}` — Update a computer (role: member) - `DELETE /computers/{id}` — Delete a computer (role: member) - `POST /computers/{id}/start` — Start a computer (role: member) - `POST /computers/{id}/stop` — Stop a computer (role: member) - `POST /computers/{id}/suspend` — Suspend a computer (role: member) - `POST /computers/{id}/restart` — Restart a computer (role: member) - `POST /computers/{id}/clone` — Clone a computer (role: member) ### Moves When a resize needs a bigger host than the one the computer is on. - `POST /computers/{id}/move` — Move a computer so a resize fits (role: member) - `GET /moves` — List moves (role: viewer) ### Control Driving the desktop: what is on screen, and acting on it. - `GET /computers/{id}/screenshot` — Take a screenshot (role: viewer) - `POST /computers/{id}/input` — Send input (role: member) - `POST /computers/{id}/exec` — Run a command (role: member) - `GET /computers/{id}/exec/{pid}` — Read a background command (role: member) - `DELETE /computers/{id}/exec/{pid}` — Stop a background command (role: member) - `GET /computers/{id}/executions/{executionId}` — Read execution metadata (role: member) - `GET /computers/{id}/executions/{executionId}/output` — Read execution output independently (role: member) - `GET /computers/{id}/windows` — List windows (role: viewer) - `POST /computers/{id}/windows/{window}` — Act on a window (role: member) - `GET /computers/{id}/clipboard` — Read the desktop clipboard (role: member) - `PUT /computers/{id}/clipboard` — Set the desktop clipboard (role: member) ### Retained results Explicit immutable output prefixes, independent of volatile execution handles. - `POST /computers/{id}/executions/{executionId}/retained-output` — Capture a retained output prefix (role: member) - `GET /computers/{id}/results/{resultId}` — Read retained result metadata (role: member) - `POST /computers/{id}/artifacts` — Publish a nominated immutable artifact (role: member) - `GET /computers/{id}/artifacts/{artifactId}` — Read immutable artifact metadata (role: member) - `GET /computers/{id}/artifacts/{artifactId}/download` — Download immutable artifact bytes (role: member) - `DELETE /computers/{id}/artifacts/{artifactId}` — Delete an immutable artifact (role: member) - `GET /computers/{id}/results/{resultId}/output` — Read an independent retained byte range (role: member) - `DELETE /computers/{id}/results/{resultId}` — Delete a retained result (role: member) ### Platform signals Passive, ephemeral daemon observations with bounded replay. - `GET /computers/{id}/signals` — Read passive platform signals (role: member) ### Activities Safe retained summaries of selected API actions. - `GET /computers/{id}/activities` — Read API activity history (role: member) - `GET /computers/{id}/activities/{activity}` — Read one API activity (role: member) - `GET /computers/{id}/activities/{activity}/results` — Read passive activity result links (role: member) ### Agents One call that drives a computer until the task is done. - `POST /computers/{id}/agent` — Run an agent loop (role: member) - `POST /chat/completions` — Drive a computer through an OpenAI-shaped endpoint (role: member) ### Files Moving files in and out of the guest. - `PUT /computers/{id}/files` — Upload a file (role: member) - `GET /computers/{id}/files` — Download a file (role: member) - `GET /computers/{id}/files/list` — List a guest directory (role: member) ### Snapshots Capturing a computer, and building from a capture. - `GET /snapshots` — List snapshots (role: viewer) - `GET /computers/{id}/snapshots` — Get what a computer holds (role: viewer) - `POST /computers/{id}/snapshots` — Take a snapshot (role: member) - `POST /snapshots/{id}/restore` — Restore a snapshot (role: member) - `POST /snapshots/{id}/clone` — Clone a snapshot into a new computer (role: member) - `DELETE /snapshots/{id}` — Delete a snapshot (role: member) ### Schedules Automatic snapshots: when they are taken, and how long they are kept. - `GET /computers/{id}/schedule` — Get the snapshot schedule (role: viewer) - `PUT /computers/{id}/schedule` — Set the snapshot schedule (role: member) - `DELETE /computers/{id}/schedule` — Remove the snapshot schedule (role: member) - `GET /retention` — Read snapshot retention (role: viewer) ### Usage What the account has used, and how much of it has settled for billing. - `GET /usage` — Read usage (role: viewer) ### Webhooks Being told when something happens, instead of asking. - `GET /webhooks` — List webhooks (role: viewer) - `POST /webhooks` — Create a webhook (role: member) - `GET /webhooks/{id}` — Read a webhook (role: viewer) - `PATCH /webhooks/{id}` — Update a webhook (role: member) - `DELETE /webhooks/{id}` — Delete a webhook (role: member) - `POST /webhooks/{id}/rotate` — Rotate a webhook secret (role: member) - `POST /webhooks/{id}/test` — Send a test delivery (role: member) - `GET /webhooks/{id}/deliveries` — List deliveries (role: viewer) ### SSH Your SSH keys, and which computers accept them. - `GET /ssh-keys` — List your SSH keys (role: viewer) - `POST /ssh-keys` — Add an SSH key (role: member) - `DELETE /ssh-keys/{id}` — Remove an SSH key (role: member) - `GET /computers/{id}/ssh` — Read a computer’s SSH setting (role: viewer) - `PUT /computers/{id}/ssh` — Switch SSH on or off (role: member) ## Errors - `400` — The request was malformed — a bad resolution string, a missing `command`, a `{pid}` or `{window}` that is not one. It is also what a well-formed request gets when the thing it names cannot be acted on and never will be. Either way it is final: the message says what is wrong, and sending it again unchanged answers the same. That is the whole difference from a `503`, which means ask again shortly. - `401` — Platform authentication required or refused: reason is missing, invalid, or revoked, with WWW-Authenticate: Bearer (invalid_token for invalid/revoked). Provider 401 retains its provider error shape. - `402` — Your plan refused this: a quota, or a payment that has not gone through. - `403` — The credential is recognized but its holder lacks the required role or membership, or the holder/account is suspended. The message identifies the refusal; replacing a valid key alone does not restore authority. - `404` — No such computer, snapshot, or endpoint; a confirmed missing guest file is no such file in the guest. - `405` — This public path exists but does not accept that method. Allow lists only its public methods, with HEAD for GET and metadata OPTIONS. - `409` — Refused for the state something is in — resizing a computer that is running, running a command on one that is stopped, restoring onto a computer that is gone. WHETHER RETRYING HELPS IS IN THE BODY, NOT THE STATUS, and this is the one thing to take from this entry. Some of these describe a state that is passing: a guest agent still inside its boot window, a guest agent busy with another call, a move already running on the account. Those clear on their own and the same request works a moment later. The rest describe a DECISION about the request you sent — the size does not fit, the computer is the wrong one for this, the saved session cannot travel — and no amount of retrying turns one of those into a yes. A client that treats 409 as uniformly transient loops forever on half of them. Where the platform can say which it is, it sends `reason` beside `error` — one word, meant to be switched on rather than read: `contention` and `starting` clear on their own, `unavailable` means the computer is not running and only starting it helps, `unsupported` means this computer cannot do it at all. Match on that and never on the sentence, which is prose and is rewritten. **Absent means no classification was given** — treat it, and any word you do not recognise, as no answer, because not every refusal here has one yet. It is what the guest routes answer: the clipboard on a stopped computer used to be a 409 indistinguishable from a clipboard whose selection was claimed for an instant, and a blanket retry loop spun against the first until its deadline. The ones worth knowing by name, because each has a next step that is not "wait": A resize past what the computer’s host can run carries `move`, an object rather than a message: `{"required":true,"possible":true}` means another host in this region could run it and the computer has to be moved there first — `POST /computers/{id}/move` with the same sizing fields is how you agree to that, and it is a separate call because relocating a computer is not something a resize should do to you quietly. `possible:false` means nowhere in the region can, and the size is the thing to change. The move itself then refuses for the things it re-decides at the moment it runs, and only one of those is worth waiting on: a computer that is running or suspended has to be stopped or resumed-and-stopped by you, a size that fits where it already is wants an ordinary resize instead, and a region with nowhere to put it wants a smaller size. `Another computer on this account is being moved right now` is the one that clears by itself — one move runs per account, and `GET /moves` says which. And the terminal socket refuses twice over: `resume_required` on a suspended computer, which clears when you start it and not before, and a computer whose hardware carries no terminal channel — one last started before interactive terminals existed — which needs a stop and a start. Not a restart, which resets the same machine, and not a retry, which never comes good. - `413` — What you sent, or asked for, is past the size limit for this endpoint — a guest file transfer, a template document, or a clipboard. The message names the limit that applied. - `416` — Your `Range` named no byte the file has. The `Content-Range` on the refusal carries the file’s actual length, so you can ask again without guessing. - `429` — You are asking faster than your plan allows. `Retry-After` says how many seconds to wait, and every response carries `RateLimit-Remaining` so you do not have to be refused to find out. The budget is spent in proportion to what a call costs rather than per request: reading one computer is the cheapest thing here, a screenshot or a listing that spans the fleet costs several times that, and launching a computer or starting an agent run costs many. The budget belongs to the account and is shared across everything on it, and any single API key may spend at most half of it. `RateLimit-Limit` is whichever of those two is binding on the request that carried it — usually the per-key half, and the account-wide figure when other keys have already spent more of it than this one has. All three `RateLimit-*` headers describe the same one, so `limit` minus `remaining` is what has been spent against it. - `500` — The application could not finish this: an unexpected exception, a build that broke, or a storage fault. Application refusals carry the request correlation ID; unexpected internal details are not returned. - `501` — No hypervisor in the fleet can build a template. Unlike a `409`, this does not clear on its own — retrying will not help until the platform can build, so treat it as an outage of the feature rather than of the moment. - `502` — The agent inside the guest is not answering, or it rejected or malformed the private command protocol for this feature. The computer is up; the upstream agent interaction failed. This is not malformed caller input and carries no retry classification. - `503` — A hypervisor could not be reached, so this could not be answered honestly. Nothing has been lost — retry shortly. On the collection reads, `allow_partial=1` accepts a knowingly short answer instead. - `504` — The guest accepted a window action but did not report its result before the deadline. The action may already have happened, so this is an uncertain outcome rather than permission to repeat it.