Desktops
Create, list, read, update, wake, hibernate, destroy, fork.
List desktops
GET /v1/desktops
| Param | In | Required | Description |
|---|---|---|---|
state | query | no | Optional lifecycle-state filter, e.g. awake |
curl -sS "$BASE/desktops?state=awake" -H "$AUTH"{ "desktops": [ /* DesktopJson[] */ ] }| Status | Meaning |
|---|---|
200 | Desktops belonging to the caller's org |
400 | Invalid state filter |
401 | Missing/invalid credentials |
Create a desktop
POST /v1/desktops
| Param | In | Required | Description |
|---|---|---|---|
Idempotency-Key | header | no | Client-supplied dedup key, scoped per-org. A repeat request with the same key (and org) returns the existing desktop (200) instead of creating a new one (201). |
Request body (CreateDesktopBody):
{
"tier": "small",
"billing_mode": "hourly",
"image_version": null,
"idle_timeout_secs": null,
"env": null,
"setup_script": null
}| Field | Type | Required | Description |
|---|---|---|---|
tier | string | yes | One of small/default/large. |
billing_mode | string | null | no | monthly or hourly; defaults to hourly when omitted/empty. |
image_version | string | null | no | |
idle_timeout_secs | integer | null | no | Seconds of guest idle time (from wake) before auto-hibernate; 0 means never. See Concepts: TTL and auto-hibernate. |
env | object | null | no | Env vars written to /root/.canto/env in the guest on first successful wake. Capped at 100 keys / 64 KiB total serialized size; every key must match ^[A-Za-z_][A-Za-z0-9_]*$ -- 400 past either cap or on any invalid key. Not echoed back by GET/POST. |
setup_script | string | null | no | First-wake setup script, run once under hostd's reserved process_id = "setup". Capped at 64 KiB -- 400 past that. Not echoed back. |
curl -sS -X POST "$BASE/desktops" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{
"tier": "small",
"billing_mode": "hourly",
"env": {"APP_ENV": "staging"},
"setup_script": "#!/bin/sh\napt-get update && apt-get install -y ripgrep\n"
}'See Concepts: env and setup_script
for the full charset/cap rules and the /root/.canto/env guest layout.
| Status | Meaning |
|---|---|
200 | Idempotency-Key already used by this org -- returns the existing desktop, not a new one |
201 | Desktop created |
400 | Invalid tier/billing_mode, malformed body, or a non-ASCII Idempotency-Key |
401 | Missing/invalid credentials |
Both 200 and 201 return a full DesktopJson.
Get a desktop
GET /v1/desktops/{id}
curl -sS "$BASE/desktops/$DESKTOP_ID" -H "$AUTH"| Status | Meaning |
|---|---|
200 | The desktop (DesktopJson) |
400 | Malformed id |
401 | Missing/invalid credentials |
404 | Desktop not found or belongs to another org |
Update a desktop
PATCH /v1/desktops/{id}
Today the only updatable field is idle_timeout_secs -- see
Concepts: TTL and auto-hibernate.
| Param | In | Required | Description |
|---|---|---|---|
id | path | yes | Desktop id |
Request body (UpdateDesktopBody, required; an empty/all-omitted body is a
no-op):
{ "idle_timeout_secs": 3600 }| Field | Type | Required | Description |
|---|---|---|---|
idle_timeout_secs | integer | null | no | 0 means "never auto-hibernate." A negative value is 400. Omitting the field is a no-op -- the current desktop is returned unchanged. |
curl -sS -X PATCH "$BASE/desktops/$DESKTOP_ID" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{"idle_timeout_secs": 3600}'| Status | Meaning |
|---|---|
200 | Updated (or unchanged, if the body carried no fields) desktop (DesktopJson) |
400 | Malformed id/body, or a negative idle_timeout_secs |
401 | Missing/invalid credentials |
404 | Desktop not found or belongs to another org |
Wake / hibernate / destroy
POST /v1/desktops/{id}/wake
POST /v1/desktops/{id}/hibernate
POST /v1/desktops/{id}/destroy
These three share an identical request/response shape.
| Param | In | Required | Description |
|---|---|---|---|
id | path | yes | Desktop id |
wait | query | no | Long-poll until the operation settles (2s cadence, 120s cap) |
Idempotency-Key | header | no | Client-supplied dedup key, scoped per-desktop |
Request body (MutationBody, optional -- an empty body is equivalent to
{}):
{ "expected_generation": null }expected_generation is an optional fencing generation the caller expects
the desktop to currently be on -- see
Concepts: generation fencing.
curl -sS -X POST "$BASE/desktops/$DESKTOP_ID/wake?wait=true" -H "$AUTH"
curl -sS -X POST "$BASE/desktops/$DESKTOP_ID/hibernate?wait=true" -H "$AUTH"
curl -sS -X POST "$BASE/desktops/$DESKTOP_ID/destroy?wait=true" -H "$AUTH"| Status | Meaning |
|---|---|
200 | Operation settled, or the ?wait=true cap was reached while still pending/running |
202 | Operation enqueued (no ?wait=true) |
400 | Malformed id or body |
401 | Missing/invalid credentials |
404 | Desktop not found or belongs to another org |
409 | Stale expected_generation or wrong desktop state |
503 | This node is not the leader |
Both 200 and 202 return an OperationJson -- see
API reference: Operations for its shape and
Concepts: Operations for the polling model.
Fork a desktop
POST /v1/desktops/{id}/fork
Creates count new, independent desktops as memory-snapshot copies of a
Hibernated source desktop. See
Concepts: Forking for the full model
(copy-on-write semantics, acknowledge_shared_state, ephemeral forks).
| Param | In | Required | Description |
|---|---|---|---|
id | path | yes | Desktop id (the fork source -- must be hibernated) |
wait | query | no | Long-poll until the operation settles (2s cadence, 120s cap) |
Idempotency-Key | header | no | Client-supplied dedup key, scoped per-desktop |
Request body (ForkBody):
{ "count": 3, "acknowledge_shared_state": true, "ephemeral": false }| Field | Type | Required | Description |
|---|---|---|---|
count | integer | yes | Number of new desktops to create from this one, 1..=20. |
acknowledge_shared_state | boolean | yes | Must be explicitly true, or the request is rejected 400. An omitted field parses as false and is rejected -- it is never treated as an implicit true. Forks duplicate in-guest tokens/credentials from the source's memory snapshot; this is your acknowledgment of that. |
ephemeral | boolean | no | If true, the new desktops get a forced 900s idle_timeout_secs (auto-hibernated after 15 minutes Awake) regardless of the source's own value; otherwise they inherit it verbatim. Defaults to false. |
curl -sS -X POST "$BASE/desktops/$DESKTOP_ID/fork?wait=true" \
-H "$AUTH" -H 'Content-Type: application/json' \
-d '{"count": 3, "acknowledge_shared_state": true}'On success, the settled operation's result carries
{"desktop_ids": ["...", "...", "..."]}. Forks come back in the
Hibernated state -- wake each one on demand, they don't start Awake.
| Status | Meaning |
|---|---|
200 | Operation settled (result: {"desktop_ids": [...]} on success), or the ?wait=true cap was reached while still pending/running |
202 | Operation enqueued (no ?wait=true) |
400 | Malformed id/body, count outside 1..=20, or acknowledge_shared_state is not true |
401 | Missing/invalid credentials |
404 | Desktop not found or belongs to another org |
409 | Source desktop is not hibernated |
413 | hostd has no free capacity for the requested count (surfaced as the settled operation's own failure unless ?wait=true) |
503 | This node is not the leader |