{"openapi":"3.1.0","info":{"title":"Canto API","description":"The public REST API for Canto cloud desktops (spec `docs/superpowers/specs/2026-08-18-canto-usability-milestone-design.md` §3). Every route is scoped to the caller's org, resolved from the bearer credential (`canto_sk_...` API key or AuthKit JWT) by `auth_middleware` -- a desktop/operation that exists but belongs to a different org is indistinguishable from one that does not exist at all (404, never 403). Desktop-mutating routes (`wake`/`hibernate`/`destroy`) return an *operation handle* (`Operation`, at `GET /v1/operations/{id}`) rather than blocking until the mutation completes: pass `?wait=true` to long-poll that handle server-side instead (2s cadence, capped at 120s -- the response is still 200/202 either way, never a timeout error; a caller that needs finer control can omit `?wait=true` and poll `GET /v1/operations/{id}` directly on its own schedule). \n\n`/v1/auth/*` (code/login/select-org/refresh, plus the onboarding `workspace`/`profile`/`invites` routes) is a dashboard-only surface that ISSUES a credential in the first place -- unauthenticated by design, entirely separate from the bearer-scoped routes documented here, and intentionally NOT part of this contract. An SDK generated from this document has no business minting sessions on a user's behalf; use a `canto_sk_...` API key (see `keys`) or an AuthKit JWT instead.\n\n## Versioning and deprecation\n\nURL-versioned under `/v1`. Additive changes only within a version; breaking changes ship as `/v2` alongside `v1`. Deprecations are signaled with RFC 9745 `Deprecation` and RFC 8594 `Sunset` response headers plus at least 90 days changelog notice (https://descanto.com/news). Policy: https://docs.descanto.com/api/overview#versioning-and-deprecation\n\n## Errors\n\nEvery non-2xx response is RFC 9457 `application/problem+json` (see the `ProblemJson` schema). `503`/`504` are retryable; honor `Retry-After` when present.\n\n## Rate limiting\n\nThrottled requests return `429` with `Retry-After` and IETF RateLimit headers (`RateLimit-Limit`/`Remaining`/`Reset`): https://docs.descanto.com/api/overview#rate-limiting","license":{"name":""},"version":"0.1.0","contact":{"name":"Descanto","email":"hello@descanto.com","url":"https://descanto.com/contact"},"x-versioning-policy":"https://docs.descanto.com/api/overview#versioning-and-deprecation","x-deprecation-signaling":"Deprecation (RFC 9745) and Sunset (RFC 8594) response headers, 90 days minimum notice","x-ratelimit-policy":"https://docs.descanto.com/api/overview#rate-limiting"},"servers":[{"url":"https://api.descanto.com","description":"Descanto production API"}],"paths":{"/v1/desktops":{"get":{"tags":["desktops"],"operationId":"listDesktops","parameters":[{"name":"state","in":"query","description":"Optional lifecycle-state filter, e.g. `awake`","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Desktops belonging to the caller's org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDesktopsResponse"}}}},"400":{"description":"Invalid state filter","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"List all desktops in the authenticated organization, with lifecycle state and metadata."},"post":{"tags":["desktops"],"operationId":"createDesktop","parameters":[{"name":"Idempotency-Key","in":"header","description":"Optional 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).","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDesktopBody"}}},"required":true},"responses":{"200":{"description":"Idempotency-Key already used by this org -- returns the existing desktop, not a new one","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopJson"}}}},"201":{"description":"Desktop created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopJson"}}}},"400":{"description":"Invalid tier/billing_mode, malformed body, or a non-ASCII Idempotency-Key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Create a new persistent desktop. Pick a tier (small/default/large) and billing mode; returns the live desktop."}},"/v1/desktops/{id}":{"get":{"tags":["desktops"],"operationId":"getDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The desktop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopJson"}}}},"400":{"description":"Malformed id","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Get one desktop by id, including its current lifecycle state."},"patch":{"tags":["desktops"],"operationId":"updateDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Optional; an empty body is a no-op","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDesktopBody"}}},"required":true},"responses":{"200":{"description":"Updated (or unchanged, if the body carried no fields) desktop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DesktopJson"}}}},"400":{"description":"Malformed id/body, or a negative idle_timeout_secs","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Update a desktop's mutable settings (e.g. name)."}},"/v1/desktops/{id}/destroy":{"post":{"tags":["desktops"],"operationId":"destroyDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","description":"Long-poll until the operation settles (2s cadence, 120s cap)","required":false,"schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","description":"Optional client-supplied dedup key, scoped per-desktop","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"description":"Optional; an empty body is equivalent to `{}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutationBody"}}},"required":true},"responses":{"200":{"description":"Operation settled, or `?wait=true` cap reached while still pending/running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"202":{"description":"Operation enqueued (no `?wait=true`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"400":{"description":"Malformed id or body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Stale `expected_generation` or wrong desktop state","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"This node is not the leader","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Permanently destroy a desktop and its state. Returns an operation handle to poll."}},"/v1/desktops/{id}/dir/{path}":{"get":{"tags":["guest-ops"],"operationId":"listDir","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","description":"Absolute guest directory path, without its leading `/`. The guest root is listed as `GET .../dir/` (a trailing slash, empty capture) -- the `.` spelling works too, but URL normalization collapses it to the same thing.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The directory's direct children, sorted by name (byte order), capped at 2000 entries (`truncated: true` past the cap)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDirResponse"}}}},"400":{"description":"Malformed id, or a path that's empty/contains a NUL byte/contains a `..` segment","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found (or belongs to another org), or the path itself does not exist","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or the path is not a directory","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The listing timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"List a directory inside the desktop's filesystem."}},"/v1/desktops/{id}/display":{"get":{"tags":["computer-use"],"operationId":"getDisplay","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The live X display geometry (one guest round trip -- there is deliberately no cached copy for hibernated desktops)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisplayResponse"}}}},"400":{"description":"Malformed id","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"501":{"description":"This desktop's host does not support the computer-use API yet","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The read timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Get the desktop's current display geometry (resolution)."},"put":{"tags":["computer-use"],"operationId":"setDisplay","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetDisplayBody"}}},"required":true},"responses":{"200":{"description":"The applied geometry, re-read from the X server after the resize (what it actually did, not what was asked). Survives hibernate/wake (VM-memory snapshots); a cold boot reverts to the image default 1024x768.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisplayResponse"}}}},"400":{"description":"Size outside 640-2560 x 480-1600","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"500":{"description":"The resize failed inside the guest for a non-RandR reason","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"501":{"description":"This desktop's image/X server does not support runtime RandR resizes (or its host predates the computer-use API) -- resolution is effectively fixed until the golden image gains RandR support","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The resize timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Set the desktop's display geometry (resolution)."}},"/v1/desktops/{id}/exec":{"post":{"tags":["guest-ops"],"operationId":"execCommand","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecBody"}}},"required":true},"responses":{"200":{"description":"`detached = false` (default): the command ran to completion in the guest","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecResponse"}}}},"202":{"description":"`detached = true`: the command was spawned in the guest; poll `GET /v1/desktops/{id}/processes/{process_id}` for its status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetachedExecResponse"}}}},"400":{"description":"Malformed id/body, or empty command","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or its control claim is already held","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The command timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Run a shell command inside an awake desktop, blocking or detached; returns stdout/stderr/exit code or a process id."}},"/v1/desktops/{id}/files-meta/{path}":{"get":{"tags":["guest-ops"],"operationId":"statFile","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","description":"Absolute guest path, without its leading `/` (e.g. `home/user/notes.txt` for `/home/user/notes.txt`)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The path's metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileStatJson"}}}},"400":{"description":"Malformed id, or a path that's empty/contains a NUL byte/contains a `..` segment","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found (or belongs to another org), or the path itself does not exist","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The stat timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Get file metadata (size, mode, mtime) without reading contents."}},"/v1/desktops/{id}/files/{path}":{"get":{"tags":["guest-ops"],"operationId":"readFile","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","description":"Absolute guest path, without its leading `/` (e.g. `home/user/notes.txt` for `/home/user/notes.txt`)","required":true,"schema":{"type":"string"}},{"name":"offset","in":"query","description":"Files v2 chunked read: return the byte window starting here instead of the whole file. The 200 response then carries `X-Canto-File-Size` (total size) and `X-Canto-Eof` (`true`/`false`) headers; an offset at or past EOF is an empty 200 body with `X-Canto-Eof: true`.","required":false,"schema":{"type":"integer","format":"int64","minimum":0}},{"name":"length","in":"query","description":"Window length for a chunked read (requires `offset`); defaults to (and is capped at) 8 MiB -- 400 above the cap.","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"responses":{"200":{"description":"Raw file content (the requested window, for a chunked read -- with `X-Canto-File-Size`/`X-Canto-Eof` headers)","content":{"application/octet-stream":{}}},"400":{"description":"Malformed id, a path that's empty/contains a NUL byte/contains a `..` segment, non-numeric `offset`/`length`, `length` without `offset`, or `length` over 8 MiB","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found (or belongs to another org), or the file itself does not exist","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or its control claim is already held","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"413":{"description":"Whole-file read of a file over 8 MiB -- switch to chunked reads (`?offset=`/`length=`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The read timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Read a file from the desktop's filesystem; supports ranged reads for large files."},"put":{"tags":["guest-ops"],"operationId":"writeFile","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","description":"Absolute guest path, without its leading `/` (e.g. `home/user/notes.txt` for `/home/user/notes.txt`)","required":true,"schema":{"type":"string"}},{"name":"append","in":"query","description":"Files v2 chunked upload: append the body iff the file's current size equals `offset` (which becomes required). A size mismatch is a 409 whose problem body carries the `currentSize` extension member, making retried chunks idempotent.","required":false,"schema":{"type":"boolean"}},{"name":"offset","in":"query","description":"Required with `append=true`: the byte offset this chunk expects the file to currently end at.","required":false,"schema":{"type":"integer","format":"int64","minimum":0}}],"requestBody":{"description":"Raw file content (one chunk when appending)","content":{"application/octet-stream":{"schema":{"type":"array","items":{"type":"integer","format":"int32","minimum":0}}}},"required":true},"responses":{"204":{"description":"File written (or chunk appended)"},"400":{"description":"Malformed id, a path that's empty/contains a NUL byte/contains a `..` segment, `append=true` without `offset`, or non-numeric params","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or an append's size!=offset guard failed (body carries `currentSize`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"413":{"description":"Body exceeds the 8 MiB per-request limit","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The write timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Write a file into the desktop's filesystem; supports chunked transfer for large files."},"delete":{"tags":["guest-ops"],"operationId":"deleteFile","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","description":"Absolute guest path, without its leading `/`. The filesystem root itself (`/`, `/.`) is rejected 400.","required":true,"schema":{"type":"string"}},{"name":"recursive","in":"query","description":"Delete a directory tree. Without it, deleting a directory is a 409.","required":false,"schema":{"type":"boolean"}}],"responses":{"204":{"description":"Deleted"},"400":{"description":"Malformed id, an invalid path (empty/NUL/`..`), or the filesystem root","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found (or belongs to another org), or the path itself does not exist (a second DELETE of the same path 404s)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or the path is a directory and `recursive` was not set","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The delete timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Delete a file from the desktop's filesystem."}},"/v1/desktops/{id}/fork":{"post":{"tags":["desktops"],"operationId":"forkDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id (the fork source -- must be Hibernated)","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","description":"Long-poll until the operation settles (2s cadence, 120s cap)","required":false,"schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","description":"Optional client-supplied dedup key, scoped per-desktop","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForkBody"}}},"required":true},"responses":{"200":{"description":"Operation settled (on success, `result` carries `{\"desktop_ids\": [...]}`), or `?wait=true` cap reached while still pending/running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"202":{"description":"Operation enqueued (no `?wait=true`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"400":{"description":"Malformed id/body, count outside 1..=20, or acknowledge_shared_state is not true","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Source desktop is not Hibernated -- fork has no `expected_generation` fencing of its own (unlike wake/hibernate/destroy), so this is the only 409 cause","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"413":{"description":"hostd has no free capacity for the requested count (surfaced as the settled operation's own failure, not this call's own status, unless `?wait=true`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"This node is not the leader","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Fork a desktop into an identical copy from its current state; the parent is never modified. Returns an operation handle."}},"/v1/desktops/{id}/generations":{"get":{"tags":["desktops"],"operationId":"listGenerations","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The desktop's snapshot History, newest first (still-restorable Generations only -- deleted ones, and ones whose bundle is being garbage-collected, are omitted). A `hibernating` snapshot-in-flight has no entry yet -- rows are recorded at Hibernate confirm.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListGenerationsResponse"}}}},"400":{"description":"Malformed id","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"List a desktop's generation (snapshot) history, newest first."}},"/v1/desktops/{id}/hibernate":{"post":{"tags":["desktops"],"operationId":"hibernateDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","description":"Long-poll until the operation settles (2s cadence, 120s cap)","required":false,"schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","description":"Optional client-supplied dedup key, scoped per-desktop","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"description":"Optional; an empty body is equivalent to `{}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutationBody"}}},"required":true},"responses":{"200":{"description":"Operation settled, or `?wait=true` cap reached while still pending/running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"202":{"description":"Operation enqueued (no `?wait=true`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"400":{"description":"Malformed id or body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Stale `expected_generation` or wrong desktop state","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"This node is not the leader","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Hibernate an awake desktop: full memory snapshot preserved, compute billing stops. Returns an operation handle."}},"/v1/desktops/{id}/input":{"post":{"tags":["computer-use"],"operationId":"sendInput","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InputBody"}}},"required":true},"responses":{"200":{"description":"The whole batch executed; `cursor` is the final cursor position. An empty `actions` list is valid and performs the cursor-position read alone. NOTE: API input is never gated by noVNC control sessions -- a human holding the control claim and an agent sending input can interleave.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InputResponse"}}}},"400":{"description":"Malformed body, unknown `type`/`button`/`direction`, or a batch cap exceeded (max 50 actions, 32 KiB total text, 10 s total waits)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"500":{"description":"An action failed mid-batch inside the guest -- actions BEFORE the failure have already executed (input is non-transactional); blind retries can double-apply","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"501":{"description":"This desktop's host does not support the computer-use API yet","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The batch timed out inside the guest","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Send a batch of mouse/keyboard input events to the desktop's GUI in a single round trip."}},"/v1/desktops/{id}/ports":{"get":{"tags":["ports"],"summary":"List a desktop's port exposures","operationId":"listDesktopPorts","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Every exposure for this desktop (tokens are never included)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPortsResponse"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}]},"post":{"tags":["ports"],"summary":"Expose a guest TCP port at a stable public URL","description":"Binds a host-side proxy for the given guest port and returns the public `https://{port}-{desktopId}.<ingress domain>` URL. Private (default) exposures return a `canto_pt_...` token exactly once -- pass it to the URL as `?canto_token=` or an `X-Canto-Token` header. The exposure survives hibernation (the URL 503s while the desktop sleeps and resumes on wake); rotate the token by deleting and re-creating the exposure.","operationId":"createDesktopPort","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortBody"}}},"required":true},"responses":{"201":{"description":"Port exposed -- `token` (private exposures only) is shown exactly once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortResponse"}}}},"400":{"description":"Malformed id/body or port out of range","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or this port is already exposed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"501":{"description":"Port ingress is not enabled on this deployment","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}]}},"/v1/desktops/{id}/ports/{port}":{"delete":{"tags":["ports"],"summary":"Stop a port exposure","description":"Path param is the guest port number (unique per desktop). Closes the live host binding (best-effort) and deletes the exposure -- the URL 404s forever after.","operationId":"deleteDesktopPort","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"port","in":"path","description":"The exposed guest port number","required":true,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"204":{"description":"Exposure stopped"},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"No such exposure (or desktop not found / other org)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}]}},"/v1/desktops/{id}/processes/{process_id}":{"get":{"tags":["guest-ops"],"operationId":"getProcess","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"process_id","in":"path","description":"Process id returned by a prior detached `POST .../exec`","required":true,"schema":{"type":"string"}},{"name":"tail_bytes","in":"query","description":"How many trailing bytes of stdout/stderr to return. Omitted/0 means hostd's own default (64 KiB); capped at 512 KiB.","required":false,"schema":{"type":"integer","format":"int32","minimum":0}}],"responses":{"200":{"description":"The process's current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessStatusResponse"}}}},"400":{"description":"Malformed id, or a non-numeric tail_bytes","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found (or belongs to another org), or process_id is unknown","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Get the status and output of a detached (background) process started via exec."}},"/v1/desktops/{id}/restore":{"post":{"tags":["desktops"],"operationId":"restoreDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","description":"Long-poll until the operation settles (2s cadence, 120s cap)","required":false,"schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","description":"Optional client-supplied dedup key, scoped per-desktop","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreBody"}}},"required":true},"responses":{"200":{"description":"Operation settled (result: `{\"state\", \"generation\", \"capturedGeneration\"}` -- a Hibernated desktop restores registry-only and stays Hibernated; an Awake one is snapshotted first, so `capturedGeneration` names the new pre-restore Generation), or `?wait=true` cap reached while still pending/running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"202":{"description":"Operation enqueued (no `?wait=true`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"400":{"description":"Malformed id or body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found/foreign org, or `generationId` unknown, belonging to another desktop, or no longer restorable because retention has begun collecting its bundle (byte-identical detail `\"generation not found\"`)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Disk-only target (not restorable until Resize lands), wrong desktop state (via `?wait=true` settle), or stale `expectedGeneration`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"This node is not the leader","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Restore a desktop to an earlier generation. Branching semantics: nothing is destroyed. Returns an operation handle."}},"/v1/desktops/{id}/screenshot":{"post":{"tags":["computer-use"],"operationId":"takeScreenshot","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"All fields optional; an empty or absent body captures the full display as binary PNG.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotBody"}}},"required":true},"responses":{"200":{"description":"The captured PNG. Default (`encoding` omitted or `\"binary\"`): raw PNG bytes with `Content-Type: image/png`. With `encoding: \"base64\"`: a JSON body carrying the base64 PNG plus its post-crop/post-scale dimensions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotBase64Response"}}}},"400":{"description":"Malformed body, partial `region`, or `scale_percent` outside 1-100","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"413":{"description":"Screenshot larger than the 8 MiB transfer cap -- retry with `region`/`scale_percent`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"500":{"description":"The screenshot failed inside the guest (X session down)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"501":{"description":"This desktop's host does not support the computer-use API yet","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"The desktop's host is currently unreachable -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"504":{"description":"The screenshot timed out inside the guest -- retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Capture a screenshot of the desktop's current display."}},"/v1/desktops/{id}/stream":{"post":{"tags":["guest-ops"],"summary":"Issue a stream ticket for an Awake desktop","description":"Returns a dial-able `tcp://host:port/?ticket=...` URL and its expiry for the desktop's display/control stream -- see `StreamTicketResponse::url`'s own field description for how the URL's host is resolved.","operationId":"createStreamTicket","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamTicketBody"}}},"required":true},"responses":{"200":{"description":"Stream ticket issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamTicketResponse"}}}},"400":{"description":"Malformed id/body or invalid claim","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Desktop is not awake, or its control claim is already held","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}]}},"/v1/desktops/{id}/wake":{"post":{"tags":["desktops"],"operationId":"wakeDesktop","parameters":[{"name":"id","in":"path","description":"Desktop id","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","description":"Long-poll until the operation settles (2s cadence, 120s cap)","required":false,"schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","description":"Optional client-supplied dedup key, scoped per-desktop","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"description":"Optional; an empty body is equivalent to `{}`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MutationBody"}}},"required":true},"responses":{"200":{"description":"Operation settled, or `?wait=true` cap reached while still pending/running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"202":{"description":"Operation enqueued (no `?wait=true`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"400":{"description":"Malformed id or body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Desktop not found or belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"409":{"description":"Stale `expected_generation` or wrong desktop state","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"503":{"description":"This node is not the leader","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Wake a hibernated desktop; resumes with memory, processes, and logins intact in ~0.5s. Returns an operation handle."}},"/v1/keys":{"get":{"tags":["keys"],"operationId":"listApiKeys","responses":{"200":{"description":"Non-revoked API keys belonging to the caller's org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKeysResponse"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"List the organization's API keys (metadata only, never secrets)."},"post":{"tags":["keys"],"operationId":"createApiKey","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyBody"}}},"required":true},"responses":{"201":{"description":"API key created -- `key` is the plaintext value, shown exactly once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyResponse"}}}},"400":{"description":"Empty or over-100-char name, or a malformed body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Create a new API key for the organization; the secret is returned once."}},"/v1/keys/{id}":{"delete":{"tags":["keys"],"operationId":"revokeApiKey","parameters":[{"name":"id","in":"path","description":"The API key id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Key revoked"},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"No such key in the caller's org (also returned for a wrong-org id or an already-revoked key -- never distinguishable from absent)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Revoke an API key immediately."}},"/v1/me":{"get":{"tags":["auth"],"operationId":"getMe","responses":{"200":{"description":"The caller's org and how they authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Get the authenticated organization and key context for the current bearer token."}},"/v1/operations/{id}":{"get":{"tags":["operations"],"operationId":"getOperation","parameters":[{"name":"id","in":"path","description":"Operation id (a UUID v4 string)","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationJson"}}}},"400":{"description":"Malformed id (not a UUID)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"Operation not found, or its desktop belongs to another org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Poll (or long-poll) an operation handle returned by wake/hibernate/destroy/fork/restore."}},"/v1/usage":{"get":{"tags":["usage"],"summary":"Per-desktop and org-total usage + projected cost for a time range","operationId":"getUsage","parameters":[{"name":"start","in":"query","description":"Range start, Unix milliseconds","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"end","in":"query","description":"Range end, Unix milliseconds","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Per-desktop and org-total usage + projected cost","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"400":{"description":"Missing/non-numeric/out-of-range `start` or `end`, or `start` after `end`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}]}},"/v1/webhooks":{"get":{"tags":["webhooks"],"operationId":"listWebhooks","responses":{"200":{"description":"Webhook endpoints belonging to the caller's org -- never includes `secret`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhooksResponse"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"List the organization's webhook endpoints."},"post":{"tags":["webhooks"],"operationId":"createWebhook","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookBody"}}},"required":true},"responses":{"201":{"description":"Webhook endpoint created -- `secret` is the `canto_whsec_...` signing secret, shown exactly once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookResponse"}}}},"400":{"description":"Invalid/non-https URL, a loopback or link-local URL host, userinfo in the URL, URL over 2000 chars, empty/unknown `events`, per-org endpoint cap reached, or a malformed body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Register a webhook endpoint for desktop lifecycle events (HMAC-signed deliveries)."}},"/v1/webhooks/{id}":{"delete":{"tags":["webhooks"],"operationId":"deleteWebhook","parameters":[{"name":"id","in":"path","description":"The webhook endpoint id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Endpoint deleted; its delivery log is cascaded and any still-pending deliveries silently stop"},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"No such webhook in the caller's org (also returned for a wrong-org id -- never distinguishable from absent)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"Delete a webhook endpoint."}},"/v1/webhooks/{id}/deliveries":{"get":{"tags":["webhooks"],"operationId":"listWebhookDeliveries","parameters":[{"name":"id","in":"path","description":"The webhook endpoint id","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max deliveries to return (default 50, max 200)","required":false,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"The endpoint's delivery log, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDeliveriesResponse"}}}},"400":{"description":"Non-numeric or out-of-range `limit`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"401":{"description":"Missing/invalid credentials","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"404":{"description":"No such webhook in the caller's org","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}},"default":{"description":"Any error. RFC 9457 problem details; 503/504 are retryable, honor Retry-After when present.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemJson"}}}}},"security":[{"bearer_auth":[]}],"summary":"List delivery attempts for a webhook, including response codes and retries."}}},"components":{"schemas":{"AuthKind":{"type":"string","description":"Which of the two credential forms [`AuthState::resolve`] verified this\nrequest through -- see this module's \"Two credential forms, one\nmiddleware\" doc section. Carried on [`OrgContext`] so a route (Task\n\"L7\": `GET /v1/me`) can report it back to the caller, e.g. so a\ndashboard session can tell it authenticated via its WorkOS AuthKit\nsession rather than an API key. `serde(rename_all = \"snake_case\")` is\nthis type's entire wire contract: `\"api_key\"` / `\"session\"`.","enum":["api_key","session"]},"CreateDesktopBody":{"type":"object","required":["tier"],"properties":{"billing_mode":{"type":["string","null"],"description":"`monthly` or `hourly`; defaults to `hourly` when omitted/empty."},"env":{"type":["object","null"],"description":"Environment variables persisted on the new desktop and re-sent on\nevery `Materialize` call (Task 6) -- written into the guest as\n`/root/.canto/env` on the desktop's first successful wake. Omitted\nor absent means \"no env\". Capped at 100 keys / 64 KiB total\nserialized size, and every key must match the POSIX env-name rule\n`^[A-Za-z_][A-Za-z0-9_]*$` -- 400 past either cap or on any invalid\nkey (`db::desktops::validate_env_and_setup`, mirrors hostd's own\nidentical validation). Not echoed back by `GET`/`POST`'s response.","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}},"idle_timeout_secs":{"type":["integer","null"],"format":"int32"},"image_version":{"type":["string","null"]},"setup_script":{"type":["string","null"],"description":"The first-wake setup script (Task 6), run once under hostd's\nreserved `process_id = \"setup\"`. Omitted/empty means \"no setup\nscript\". Capped at 64 KiB -- 400 past that. Not echoed back."},"tier":{"type":"string","description":"One of `small`/`default`/`large`."}}},"CreateKeyBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"CreateKeyResponse":{"type":"object","description":"The wire shape `POST /v1/keys` returns -- the ONLY response that ever\ncarries the plaintext `key`. See this module's doc comment.","required":["id","name","key","keyLast4","createdAt"],"properties":{"createdAt":{"type":"string","description":"RFC 3339 -- `chrono::DateTime<Utc>` doesn't implement utoipa's\n`ToSchema` (no other route in this module family has needed a\ntimestamp field yet), so this and every other wire timestamp in this\nmodule is a formatted `String`, not the `chrono` type directly."},"id":{"type":"string"},"key":{"type":"string"},"keyLast4":{"type":"string"},"name":{"type":"string"}}},"CreatePortBody":{"type":"object","required":["port"],"properties":{"port":{"type":"integer","format":"int32","description":"The guest TCP port to expose, 1..=65535.","minimum":0},"public":{"type":"boolean","description":"Serve the port to the whole internet with no token (default false:\na `canto_pt_...` token is minted and required at the ingress)."}}},"CreatePortResponse":{"type":"object","description":"The wire shape `POST .../ports` returns -- the ONLY response that ever\ncarries the plaintext `token` (present only for private exposures, and\nonly at create time).","required":["id","port","public","url","status","created_at"],"properties":{"created_at":{"type":"string","description":"RFC 3339."},"id":{"type":"string"},"port":{"type":"integer","format":"int32","minimum":0},"public":{"type":"boolean"},"status":{"type":"string","description":"`bound` (live listener on the host) or `unbound`."},"token":{"type":["string","null"],"description":"The `canto_pt_...` bearer for private exposures, shown exactly once\n(hash-stored, never recoverable). Absent -- not null -- for public\nexposures."},"url":{"type":"string","description":"`https://{port}-{desktopId}.{ingress.external_domain}` -- stable\nacross hibernation (503s while unbound, resumes without client\naction once rebound)."}}},"CreateWebhookBody":{"type":"object","required":["url","events"],"properties":{"events":{"type":"array","items":{"type":"string"}},"url":{"type":"string"}}},"CreateWebhookResponse":{"type":"object","description":"The wire shape `POST /v1/webhooks` returns -- the ONLY response that\never carries `secret`. See this module's doc comment.","required":["id","url","events","secret","createdAt"],"properties":{"createdAt":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"secret":{"type":"string","description":"The `canto_whsec_...` signing secret -- shown exactly once, here."},"url":{"type":"string"}}},"CursorJson":{"type":"object","required":["x","y"],"properties":{"x":{"type":"integer","format":"int32","minimum":0},"y":{"type":"integer","format":"int32","minimum":0}}},"DesktopJson":{"type":"object","description":"The wire shape of a `Desktop`, per the plan: snake_case fields, tier/\nstate as lowercase strings (`Tier::as_str`/`DesktopState::as_str`).","required":["id","org_id","tier","state","image_version","generation","idle_timeout_secs","billing_mode","ephemeral"],"properties":{"billing_mode":{"type":"string","description":"`monthly` or `hourly`."},"current_generation_id":{"type":["string","null"]},"ephemeral":{"type":"boolean","description":"`true` only for a fork created with `ephemeral: true` (see `POST\n.../fork`'s request body) -- its `idle_timeout_secs` was forced to\n900 at creation regardless of its source's own value."},"generation":{"type":"integer","format":"int64","minimum":0},"host_id":{"type":["string","null"]},"id":{"type":"string"},"idle_timeout_secs":{"type":"integer","format":"int32"},"image_version":{"type":"string"},"org_id":{"type":"string"},"state":{"type":"string","description":"One of `provisioning`/`awake`/`hibernating`/`hibernated`/`waking`/`destroyed`."},"tier":{"type":"string","description":"One of `small`/`default`/`large`."}}},"DesktopUsageJson":{"type":"object","required":["desktop_id","awake_seconds","weighted_awake_seconds","desktop_months","billing_mode","projected_cost_cents"],"properties":{"awake_seconds":{"type":"number","format":"double"},"billing_mode":{"type":"string","description":"`monthly` or `hourly`; empty string on the (practically unreachable)\njoin-miss fallback -- see `api::project_desktop_usage`'s doc comment."},"desktop_id":{"type":"string"},"desktop_months":{"type":"number","format":"double"},"projected_cost_cents":{"type":"integer","format":"int64"},"weighted_awake_seconds":{"type":"number","format":"double"}}},"DetachedExecResponse":{"type":"object","description":"The `202` body a `detached = true` exec answers with -- see `ExecBody`'s\ndoc comment. `process_id` is what `GET .../processes/{process_id}`\npolls.","required":["process_id"],"properties":{"process_id":{"type":"string"}}},"DirEntryJson":{"type":"object","description":"One `GET .../dir/{path}` entry.","required":["name","fileType","sizeBytes","modifiedAt"],"properties":{"fileType":{"type":"string","description":"`\"file\"` | `\"directory\"` | `\"symlink\"` | `\"other\"`."},"modifiedAt":{"type":"string","description":"RFC 3339."},"name":{"type":"string"},"sizeBytes":{"type":"integer","format":"int64","minimum":0}}},"DisplayResponse":{"type":"object","description":"The live X display geometry.","required":["width","height"],"properties":{"height":{"type":"integer","format":"int32","minimum":0},"width":{"type":"integer","format":"int32","minimum":0}}},"ExecBody":{"type":"object","required":["command"],"properties":{"command":{"type":"string"},"detached":{"type":"boolean","description":"Task 3: if `true`, the command is spawned inside the guest and this\nroute answers `202 {process_id}` (see [`DetachedExecResponse`])\ninstead of waiting for it to exit. Defaults to `false` (the\noriginal, blocking behavior)."},"timeout_secs":{"type":["integer","null"],"format":"int64","description":"Defaults to 60s when omitted; clamped (never rejected) to 300s.\nIgnored when `detached = true` (a detached command is spawned, not\nwaited on, so there's nothing here for a timeout to bound).","minimum":0}}},"ExecResponse":{"type":"object","required":["exit_code","stdout","stderr"],"properties":{"exit_code":{"type":"integer","format":"int32"},"stderr":{"type":"string"},"stdout":{"type":"string"}}},"FileStatJson":{"type":"object","description":"`GET .../files-meta/{path}`'s response.","required":["path","fileType","sizeBytes","modifiedAt","mode"],"properties":{"fileType":{"type":"string","description":"`\"file\"` | `\"directory\"` | `\"symlink\"` | `\"other\"`."},"mode":{"type":"string","description":"Permission bits as octal text, e.g. `\"644\"`."},"modifiedAt":{"type":"string","description":"RFC 3339 -- see `keys.rs`'s identical wire-timestamp convention\n(`chrono::DateTime` lacks utoipa's `ToSchema`)."},"path":{"type":"string"},"sizeBytes":{"type":"integer","format":"int64","minimum":0}}},"ForkBody":{"type":"object","description":"`POST /v1/desktops/{id}/fork`'s request body (Task 5). `count` must be in\n`1..=20` (mirrors T4's hostd-side bound); `acknowledge_shared_state` has\nno default -- an omitted field parses as `false` (`#[serde(default)]`)\nand is then explicitly rejected 400 by [`fork`], rather than accepted as\nif the caller meant `true` (spec §2: hostd cannot tell a pristine\ngeneration from a non-pristine one, so this must always be an explicit,\naffirmative `true`).","required":["count"],"properties":{"acknowledge_shared_state":{"type":"boolean","description":"Must be `true`, or the request is rejected 400 -- see this struct's\ndoc comment."},"count":{"type":"integer","format":"int32","description":"Number of new desktops to create from this one, `1..=20`.","minimum":0},"ephemeral":{"type":"boolean","description":"If `true`, the new desktops get a forced 900s `idle_timeout_secs`\n(auto-hibernated by `idle.rs` after 15 minutes Awake) regardless of\nwhat this desktop's own `idle_timeout_secs` is; otherwise they\ninherit it verbatim. Defaults to `false`."}}},"GenerationJson":{"type":"object","description":"One History entry.","required":["id","kind","createdAt","retentionBucket","current"],"properties":{"createdAt":{"type":"string","description":"RFC 3339 -- see `keys.rs`'s identical wire-timestamp convention."},"current":{"type":"boolean","description":"Whether this Generation is the desktop's current head."},"id":{"type":"string"},"kind":{"type":"string","description":"`\"full\"` or `\"disk_only\"`."},"retentionBucket":{"type":"string","description":"`\"recent\"` (< 1h old), `\"hourly\"` (< 24h), or `\"daily\"` -- computed\nat read time with the same boundaries the retention reconciler\nuses ([`retention_bucket`]), so the API can never disagree with\nthe GC."},"tombstonedAt":{"type":["string","null"],"description":"Set only on a destroyed desktop's rows (the 7-day recovery\nwindow). RFC 3339."}}},"InputActionJson":{"oneOf":[{"type":"object","description":"Absolute cursor move.","required":["x","y","type"],"properties":{"type":{"type":"string","enum":["mouse_move"]},"x":{"type":"integer","format":"int32","minimum":0},"y":{"type":"integer","format":"int32","minimum":0}}},{"type":"object","description":"Click (optionally moving first via `x`+`y`; `double` clicks twice,\n120 ms apart).","required":["type"],"properties":{"button":{"type":["string","null"]},"double":{"type":["boolean","null"]},"type":{"type":"string","enum":["click"]},"x":{"type":["integer","null"],"format":"int32","minimum":0},"y":{"type":["integer","null"],"format":"int32","minimum":0}}},{"type":"object","description":"Press-and-hold a button (manual drag choreography).","required":["button","type"],"properties":{"button":{"type":"string"},"type":{"type":"string","enum":["button_press"]}}},{"type":"object","description":"Release a held button.","required":["button","type"],"properties":{"button":{"type":"string"},"type":{"type":"string","enum":["button_release"]}}},{"type":"object","description":"Drag from `from` (or the current cursor when omitted) to `to`.","required":["to","type"],"properties":{"button":{"type":["string","null"]},"from":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PointJson"}]},"to":{"$ref":"#/components/schemas/PointJson"},"type":{"type":"string","enum":["drag"]}}},{"type":"object","description":"Scroll the wheel `amount` notches (1-50, default 1).","required":["direction","type"],"properties":{"amount":{"type":["integer","null"],"format":"int64","minimum":0},"direction":{"type":"string"},"type":{"type":"string","enum":["scroll"]}}},{"type":"object","description":"Type text (1-8192 bytes UTF-8 per action, no NUL; `delay_ms` 0-500,\ndefault 12).","required":["text","type"],"properties":{"delay_ms":{"type":["integer","null"],"format":"int64","minimum":0},"text":{"type":"string"},"type":{"type":"string","enum":["type"]}}},{"type":"object","description":"Press a key combo (`+`-joined X keysyms, <= 8 tokens, each token\n`[A-Za-z0-9_]{1,32}` -- e.g. `\"ctrl+shift+t\"`).","required":["combo","type"],"properties":{"combo":{"type":"string"},"type":{"type":"string","enum":["key"]}}},{"type":"object","description":"In-batch pause, 1-5000 ms (sum of all waits <= 10000 ms per batch).","required":["ms","type"],"properties":{"ms":{"type":"integer","format":"int64","minimum":0},"type":{"type":"string","enum":["wait"]}}}],"description":"One typed input action -- the wire (snake_case, `type`-discriminated)\nshape of the computer-use spec §3.2's action table."},"InputBody":{"type":"object","required":["actions"],"properties":{"actions":{"type":"array","items":{"$ref":"#/components/schemas/InputActionJson"},"description":"Executed in order, one guest round trip total. An EMPTY list is\nvalid and performs the cursor-position read alone. A mid-batch\nfailure returns 500 -- actions before the failure HAVE already\nexecuted (input is inherently non-transactional)."}}},"InputResponse":{"type":"object","description":"Always the FINAL cursor position after the whole batch ran.","required":["cursor"],"properties":{"cursor":{"$ref":"#/components/schemas/CursorJson"}}},"KeySummaryJson":{"type":"object","description":"One entry in `GET /v1/keys`'s `keys` array -- `key_last4` only, never the\nplaintext.","required":["id","name","keyLast4","createdAt"],"properties":{"createdAt":{"type":"string"},"id":{"type":"string"},"keyLast4":{"type":"string"},"lastUsedAt":{"type":["string","null"]},"name":{"type":"string"}}},"ListDeliveriesResponse":{"type":"object","required":["deliveries"],"properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryJson"}}}},"ListDesktopsResponse":{"type":"object","required":["desktops"],"properties":{"desktops":{"type":"array","items":{"$ref":"#/components/schemas/DesktopJson"}}}},"ListDirResponse":{"type":"object","required":["path","entries","truncated"],"properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/DirEntryJson"}},"path":{"type":"string"},"truncated":{"type":"boolean","description":"`true` when the listing was cut at the 2000-entry cap (or lost\nrecords to the guest-side output bound)."}}},"ListGenerationsResponse":{"type":"object","required":["generations"],"properties":{"generations":{"type":"array","items":{"$ref":"#/components/schemas/GenerationJson"}}}},"ListKeysResponse":{"type":"object","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/KeySummaryJson"}}}},"ListPortsResponse":{"type":"object","required":["ports"],"properties":{"ports":{"type":"array","items":{"$ref":"#/components/schemas/PortJson"}}}},"ListWebhooksResponse":{"type":"object","required":["webhooks"],"properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSummaryJson"}}}},"MeResponse":{"type":"object","description":"The wire shape `GET /v1/me` returns.","required":["org_id","auth_kind"],"properties":{"auth_kind":{"$ref":"#/components/schemas/AuthKind","description":"`\"api_key\"` or `\"session\"` -- see [`AuthKind`]."},"org_id":{"type":"string"}}},"MutationBody":{"type":"object","properties":{"expected_generation":{"type":["integer","null"],"format":"int64","description":"Optional fencing generation the caller expects the desktop to\ncurrently be on; a mismatch settles the operation `failed` with a\n\"generation mismatch\" error, surfaced as 409 by `?wait=true`.","minimum":0}}},"OperationJson":{"type":"object","description":"The wire shape of an `Operation`, per the plan: snake_case fields,\nkind/state as lowercase strings.","required":["id","desktop_id","kind","state"],"properties":{"desktop_id":{"type":"string"},"error":{"type":["string","null"]},"id":{"type":"string"},"kind":{"type":"string"},"result":{},"state":{"type":"string"}}},"PointJson":{"type":"object","required":["x","y"],"properties":{"x":{"type":"integer","format":"int32","minimum":0},"y":{"type":"integer","format":"int32","minimum":0}}},"PortJson":{"type":"object","description":"One entry in `GET .../ports`' `ports` array -- never includes `token`.","required":["id","port","public","url","status","created_at"],"properties":{"created_at":{"type":"string"},"id":{"type":"string"},"port":{"type":"integer","format":"int32","minimum":0},"public":{"type":"boolean"},"status":{"type":"string","description":"`bound` (live listener on the host) or `unbound` (desktop\nhibernated, or rebind-on-wake failed -- the URL 503s while unbound\nand starts working again without client action once rebound)."},"url":{"type":"string"}}},"ProblemJson":{"type":"object","description":"An RFC 9457 (\"Problem Details for HTTP APIs\") error body.\n\n`detail` must never contain a raw credential, or (per this module's \"the\nmapping table\" section) the raw text of an internal error -- every\n`ApiError::Internal` conversion below builds it from a fixed string.\n\nUsed by Task 9's OpenAPI doc (`http::openapi`) as the body schema for\nevery non-2xx response every route documents -- served with\n`Content-Type: application/problem+json` (see [`ProblemJson`]'s\n`IntoResponse` impl below), never plain `application/json`.","required":["type","title","status","detail"],"properties":{"currentSize":{"type":["integer","null"],"format":"int64","description":"Files-v2 extension member (RFC 9457 allows arbitrary extensions):\nset only on the 409 an offset-guarded append (`PUT\n.../files/{path}?append=true&offset=N`) returns when the file's\ncurrent size doesn't equal `offset` -- carries that current size so\na chunking client can resume (or treat a replayed chunk as already\napplied) without a follow-up stat. camelCase on the wire per the\ntrack's house rule for all new JSON.","minimum":0},"detail":{"type":"string"},"operation_id":{"type":["string","null"],"description":"Set when the problem concerns a specific operation (e.g. a\n`?wait=true` poll that observed the op fail) -- optional, and absent\nfrom the wire body entirely rather than `null` when unset."},"status":{"type":"integer","format":"int32","minimum":0},"title":{"type":"string"},"type":{"type":"string"}}},"ProcessStatusResponse":{"type":"object","required":["process_id","status","exit_code","stdout_tail","stderr_tail"],"properties":{"exit_code":{"type":"integer","format":"int32","description":"Valid only when `status == \"exited\"`."},"process_id":{"type":"string"},"status":{"type":"string","description":"`\"running\"` | `\"exited\"` | `\"lost\"`."},"stderr_tail":{"type":"string"},"stdout_tail":{"type":"string"}}},"RegionJson":{"type":"object","description":"A screenshot crop rectangle in screen pixels. All four fields required\ntogether; an out-of-screen region is clamped by the guest's own scrot.","required":["x","y","width","height"],"properties":{"height":{"type":"integer","format":"int32","minimum":0},"width":{"type":"integer","format":"int32","minimum":0},"x":{"type":"integer","format":"int32","minimum":0},"y":{"type":"integer","format":"int32","minimum":0}}},"RestoreBody":{"type":"object","description":"`POST .../restore`'s request body (camelCase per the track's house\nrule for all new JSON).","required":["generationId"],"properties":{"expectedGeneration":{"type":["integer","null"],"format":"int64","description":"Optional fencing CAS, same semantics as wake/hibernate/destroy.","minimum":0},"generationId":{"type":"string","description":"The Generation to restore -- must belong to this desktop's own\nHistory (see `GET .../generations`)."}}},"ScreenshotBase64Response":{"type":"object","description":"The `encoding: \"base64\"` response shape -- `width`/`height` are the\nreturned image's own dimensions (post-crop, post-scale), parsed by the\nhost from the PNG IHDR.","required":["data_base64","width","height"],"properties":{"data_base64":{"type":"string"},"height":{"type":"integer","format":"int32","minimum":0},"width":{"type":"integer","format":"int32","minimum":0}}},"ScreenshotBody":{"type":"object","properties":{"encoding":{"type":["string","null"],"description":"`\"binary\"` (default: raw PNG bytes, `Content-Type: image/png`) or\n`\"base64\"` (JSON `{data_base64, width, height}`)."},"region":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/RegionJson","description":"Optional crop rectangle in screen pixels."}]},"scale_percent":{"type":["integer","null"],"format":"int32","description":"Optional integer 1-100: downscale to this percent (scrot's\nthumbnail generator). Omitted = full size.","minimum":0}}},"SetDisplayBody":{"type":"object","required":["width","height"],"properties":{"height":{"type":"integer","format":"int32","description":"480-1600.","minimum":0},"width":{"type":"integer","format":"int32","description":"640-2560.","minimum":0}}},"StreamTicketBody":{"type":"object","required":["claim"],"properties":{"claim":{"type":"string","description":"`view` or `control`."},"takeover":{"type":"boolean","description":"Forcibly take over an existing `control` claim."}}},"StreamTicketResponse":{"type":"object","required":["url","expires_unix"],"properties":{"expires_unix":{"type":"integer","format":"int64","minimum":0},"url":{"type":"string","description":"A dial-able `tcp://host:port/?ticket=...` URL. `outcome.url` (as\nreturned by `HostAgent::issue_stream_session`) is hostd's own raw\nanswer, which always embeds hostd's `0.0.0.0` bind address rather\nthan a dial-able one -- as of Task 10, `RealHostAgent::\nissue_stream_session` rewrites the host part to the desktop's host's\nexternally reachable address (`hosts.addr`) before this value ever\nreaches this route (see `hostagent::rewrite_stream_url`'s doc\ncomment for the exact rule); this route itself does not duplicate\nthat rewrite, it's already done by the time `outcome` arrives here."},"ws_url":{"type":["string","null"],"description":"Ingress track (§3.4): a PATH (not an absolute URL) to this ticket's\nWebSocket VNC bridge -- `/v1/desktops/{id}/vnc/{nonce}`. The\ndashboard prepends its own API base and flips the scheme to\n`ws(s)`; keeping this relative keeps controld out of the business\nof knowing its public hostname. Present whenever the ticket URL\nparses into a dialable `host:port` (always, for real hostd\noutcomes); the nonce is single-controld-process state -- see\n`http::vnc`'s module doc comment."}}},"UpdateDesktopBody":{"type":"object","properties":{"idle_timeout_secs":{"type":["integer","null"],"format":"int32","description":"The only updatable field today. `0` means \"never auto-hibernate\"\n(see `idle.rs`); a negative value is 400. Omitting the field is a\nno-op -- the current desktop is returned unchanged, per PATCH\nsemantics (mirrors `controld.proto`'s `UpdateDesktopRequest` doc\ncomment)."}}},"UsageResponse":{"type":"object","required":["per_desktop","org_awake_seconds_weighted","org_desktop_months","org_projected_cost_cents"],"properties":{"org_awake_seconds_weighted":{"type":"number","format":"double"},"org_desktop_months":{"type":"number","format":"double"},"org_projected_cost_cents":{"type":"integer","format":"int64"},"per_desktop":{"type":"array","items":{"$ref":"#/components/schemas/DesktopUsageJson"}}}},"WebhookDeliveryJson":{"type":"object","description":"One entry in `GET /v1/webhooks/{id}/deliveries`.","required":["id","eventType","state","attempts","createdAt","payload"],"properties":{"attempts":{"type":"integer","format":"int32"},"createdAt":{"type":"string"},"deliveredAt":{"type":["string","null"]},"eventType":{"type":"string"},"id":{"type":"string"},"lastError":{"type":["string","null"]},"lastStatus":{"type":["integer","null"],"format":"int32"},"nextAttemptAt":{"type":["string","null"],"description":"Null once the delivery has settled (`delivered`/`failed`)."},"payload":{"description":"The exact JSON body that is/was POSTed to the endpoint."},"state":{"type":"string","description":"`pending` | `delivered` | `failed`."}}},"WebhookSummaryJson":{"type":"object","description":"One entry in `GET /v1/webhooks` -- never includes `secret`.","required":["id","url","events","createdAt"],"properties":{"createdAt":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"id":{"type":"string"},"url":{"type":"string"}}}},"securitySchemes":{"bearer_auth":{"type":"http","scheme":"bearer","bearerFormat":"canto_sk_... API key, or an AuthKit JWT access token"}}},"tags":[{"name":"desktops","description":"Desktop lifecycle: create/list/get and the wake/hibernate/destroy/fork mutations."},{"name":"guest-ops","description":"Operations reaching into an Awake desktop's guest: stream tickets, exec, file read/write."},{"name":"computer-use","description":"The agent computer-use loop against an Awake desktop's display: screenshots, typed mouse/keyboard input batches (one guest round trip each), and display geometry get/set. Input is never gated by noVNC control sessions."},{"name":"operations","description":"Read-only lookup of a control-plane operation handle."},{"name":"usage","description":"Per-desktop and org-total usage + projected cost."},{"name":"auth","description":"Caller identity: who-am-I resolution from the bearer credential."},{"name":"keys","description":"API key management: create/list/revoke an org's own `canto_sk_...` keys. Plaintext is returned exactly once, on create."},{"name":"ports","description":"Port ingress: expose a guest TCP port at a stable public `https://{port}-{desktopId}.<domain>` URL, token-gated by default. The `canto_pt_...` token is returned exactly once, on create."},{"name":"webhooks","description":"Lifecycle webhooks: create/list/delete org-scoped endpoints and read their delivery logs. The `canto_whsec_...` signing secret is returned exactly once, on create; deliveries are signed `Canto-Signature: t=<unix-secs>,v1=hex(HMAC-SHA256(secret, \"{t}.{body}\"))`."}]}