---
title: "List templates"
method: GET
path: "/templates"
tags: ["Templates"]
---

# List templates

`GET /templates`

## Request

- Base URL: `https://app.mandala.computer/api/v1`
- URL: `https://app.mandala.computer/api/v1/templates`
- Auth: HTTP bearer

The images a computer can be built from, with the CPU and memory each one defaults to and the disk it cannot be built below. For the templates we publish, `name` is what you pass as `template` to create; for one you published, pass its `ref` — a short name is not resolved to your templates and falls back to the default template, or, while this list is short and carries `X-GC-Incomplete`, is refused with a `503` (see `template` on `POST /computers`).

If a host cannot be reached the list may be short, and then carries `X-GC-Incomplete`; this listing answers with what it has rather than a `503`.

A template is an image rather than an operating system — `hermes`, `claude` and `openclaw` are the base Linux desktop with an agent already installed, `coding` is the same desktop with ten coding-agent CLIs on it (Claude Code, Codex, Gemini CLI, Qwen Code, opencode, goose, Cline, Aider, Open Interpreter and a Cursor CLI installer), `managed-agents` runs Anthropic’s Claude Managed Agents self-hosted worker as a service once its environment key is bound as a secret, and `omarchy` is a different desktop entirely (Arch + Hyprland) — so read `label` rather than assuming this list is a choice of OS. Only the templates your plan may launch are returned.

`omarchy` is a Wayland desktop, and that is worth knowing before you drive one. Screenshots, input and `exec` behave exactly as they do everywhere else: they are taken at the hypervisor, below the guest, so nothing about them changes. The window, window-action and clipboard routes are answered through the compositor instead of through X, which shows up in two places — a window id is the compositor address rather than an X window id (still opaque, still handed back the same way), and moving or resizing a window the compositor is TILING is refused rather than silently ignored, because a tiled window’s geometry belongs to the layout. Float it first.

Requires the `viewer` role or higher.

## Response `200`

Every template available to your account.

- Template[]
  - `name` string: The short name of the template, and what a computer reports as its own `template`. Accepted as `template` on a create for the templates we publish; one you published is named by its `ref`.
  - `ref` string: The pinned `namespace/name@version` for this template. Also accepted as `template` on a create, and the form to use when it matters that you get exactly this template and this version — a short name resolves to whatever the host currently has under it.
  - `label` string: Human-readable name.
  - `os` string: `linux`. (`windows` is not currently offered on any plan.)
  - `desktop` string: The display protocol this template’s desktop speaks: `wayland`, `x11`, or absent (X11, or a host too old to say); branch on `=== "wayland"`. Screenshots, input and `exec` are identical either way — they are taken below the guest — but two things differ on a Wayland template. A window `id` is the compositor’s address rather than an X window id (opaque and handed back the same way, but not comparable across the two), and moving or resizing a window the compositor is TILING is refused rather than silently ignored, because a tiled window’s geometry belongs to the layout. Absent also on a host deployed before this field existed, which is why it is not defaulted to `x11` here.
  - `icon` string: A slug naming a mark this console has an asset for, e.g. `claude` or `debian`. Cosmetic — nothing here validates it against a list, and an unrecognised value or none just means the template has no particular icon.
  - `cpu` integer: The default this template builds at.
  - `ram_mb` integer
  - `disk_gb` integer: The FLOOR this template builds at, not a default like the two above — a create asking for less is raised to this and charged at it.

## Other responses

- `401`: Platform authentication required or refused: reason is missing, invalid, or revoked, with WWW-Authenticate: Bearer (invalid_token for invalid/revoked).
- `403`: The credential is recognized but its holder lacks the required role or membership, or the holder/account is suspended, or the holder has not verified their email address. An ordinary denial for too low a role may carry no `reason`. The email one carries none either, and its message begins `Verify your email before`: it answers a create, a clone of a computer or a snapshot, a new API key and a new SSH key, and is cleared by verifying the address in the dashboard. `reason: "revoked"` marks authority that is gone: a suspended account or a lost membership (refused as the request arrives, or if it happens while the request is in flight), or a role that was lowered while the request was in flight. The message identifies the refusal; replacing a valid key alone does not restore authority, and sending the same call again does not help.
- `405`: This public path exists but does not accept that method. Allow lists only its public methods, with HEAD for GET and metadata OPTIONS.
- `429`: You are asking faster than your plan allows. `Retry-After` says how many seconds to wait, and every metered response — anything past authentication and the re-check that your credential’s authority still holds — carries `RateLimit-Remaining`, so you do not have to be refused to find out. A `401`, a `403` from that re-check, an `OPTIONS` answer and an unexpected `500` carry none. The budget is spent in proportion to what a call costs rather than per request. What a call costs, in budget units, by kind (each operation’s exact figure is its `x-rate-cost` in the OpenAPI document): 1 to read one thing; 5 for a listing that spans the fleet, and 5 for a call into a running guest such as a screenshot or an exec; 10 for a power change and 10 to move file bytes; 25 for `GET /usage`, `GET /credits`, creating a public URL, and creating, rotating or testing a webhook; 50 to capture a snapshot; 100 to create, clone or restore a computer, and 100 to start an agent run, whose actions are then charged as they happen. The account-wide budget, as what an idle caller may spend at once and what refills each minute: No plan 1200 at once and 1200 a minute; Solo 4800 at once and 4800 a minute; Studio 12000 at once and 12000 a minute; Swarm 21600 at once and 21600 a minute. A single API key gets half of either. A `401` for a missing or unusable credential costs nothing: it is refused before anything is metered. A few refusals at the door cost one unit: a `403` because the role is too low or the route refuses an API key confined to a workspace, a `404` for a path that matches nothing, a `405`, and an `Idempotency-Key` answered from its stored result or still in progress. Everything else is charged in full when it is admitted, before it is carried out, so a refusal after that has spent the whole cost: a `400`, `402` or `409` from the route, a `403` its handler sends (on most routes, a suspended account included), and a model provider’s `401` on an agent run. 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. A few `429`s are not about this budget, say so in their message, and can carry no `Retry-After`: a model provider’s rate limit relayed on an agent run. The cap on forks of one snapshot waiting for their secrets sends its own bounded expiry-based `Retry-After` with `reason: "contention"` (see `POST /snapshots/{id}/clone`).
- `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.
- `503`: Something this depends on — a hypervisor, a store, or a host’s capacity — could not answer or take this right now. A read can be retried shortly. A change answered `503` may or may not have happened — a failure after the request was sent is answered the same way, with or without a warning in the message — so read the current state before retrying anything that is not safe to repeat, such as a create or a command. On the collection reads, `allow_partial=1` accepts a knowingly short answer instead.

---

[API](https://skmtc.dev/mandala/apis/mandala-computer-api.md) · [All operations](https://skmtc.dev/mandala/apis/mandala-computer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mandala/apis/mandala-computer-api/revisions/2afbb87fa703?raw)
