desk-ops

Embed
Preview of the card
<a href="https://gaiadesk.net/docs/ref-desk-ops"><img src="https://gaiadesk.net/docs/_api/public/orgs/89a5df38-be2c-4963-8823-21d4bd40a2ab/spaces/57b99ea0-d45a-4986-8aa6-6b0683c9fe1d/pages/296a123f-0d4f-4dec-8a90-f657e63dae10/social-card.svg?v=3e7ae33eec8b" alt="desk-ops" width="600" height="315"></a>
POSThttps://api.gaiadesk.net/v1/desks/{id}/execScope exec

Run one command on the desk; the same result as `gaiadesk-cli exec --json`.

A command that ran answers 200 whatever its exit code (exit, remote_code, error), as exec --json prints it; one the desk refused to start answers 200 with exit 254 and a refused error, also as the CLI does. ?stream=1 answers Server-Sent Events instead: stdout, stderr, then exit (or error), each event's data one ExecEvent object, as exec --json-stream prints them. A refusal in the stream's first moments is an HTTP error; a failure after the stream started (the desk's, or the desk lost) is its last event, error, and the call's audit event records that failure, not the 200. Output buffered for a JSON answer is capped at 8 MB (truncated); every call at 15 minutes (timeout_secs defaults to, and is held under, that limit — start a job for longer work). A streamed call is never replayed for an Idempotency-Key.

The desk verifies the credential itself, as for gaiadesk-cli: from an API key, a scoped agent token in X-GaiaDesk-Desk-Token (its scopes, cwd confinement and low-privilege user apply); from a signed-in person, their own account on their own desk. A desk whose owner turned off "Allow commands from the GaiaDesk API" answers 403 desk_opted_out.

Administrator work is NOT available through the API: a body with "admin": true is refused (200, exit 254, error.reason admin_not_via_api) before anything is asked or run. Run administrator work with gaiadesk-cli exec --admin or the SDK's direct transport (docs/admin-access.md).

Parameters

idstringpathrequired
The desk's nine-digit id.
GaiaDesk-E2Estringheader
End-to-end encrypted: the sealed request (ApiE2eRequest, as base64url of its JSON) in place of a plaintext body or parameters. The answer is then sealed too (ApiE2eAnswer, ApiE2eStreamEvent, ApiE2eFrame). A POST may instead send the body {"e2e": …} (ApiE2eBody). See the "End-to-end encryption" section of docs/api/README.md.
Idempotency-Keystringheader
Up to 255 printable ASCII characters. A retry with the same key and the same request within 24 hours gets the first answer again (Idempotent-Replayed: true); the same key with a different request is refused (usage, idempotency_key_reused).
wake_sintegerquery
If the desk is asleep, ring it and wait up to this long (default 60, at most 120).
streamintegerquery
1: answer Server-Sent Events.
0 | 1

Request body

application/json
Example
{
  "admin": {},
  "argv": [
    "string"
  ],
  "command": "string",
  "cwd": "string",
  "env": {},
  "kill_on_disconnect": true,
  "shell": "default",
  "stdin": "string",
  "timeout_secs": 0
}
ApiExecSpec
admin
Refused when true (admin_not_via_api).
argvstring[]
commandstring
cwdstring
The directory it starts in on the desk (relative: from the desk user's home, or a confined token's directory).
envobject
Environment variables for it, on top of the desk's (sent in the request, never logged).
kill_on_disconnectboolean
Kill the process tree when the caller disconnects (exec --kill-tree-on-disconnect). Without this, the process keeps running after the client exits.
shellstring
Which shell runs a command on the desk.
= default
stdinstring
Text for its stdin, then end of input. Absent: stdin is closed (the null device), unless [crate::ExecInput] feeds it.
timeout_secsnumber
Stop it after this many seconds (0: no limit; absent: 30 minutes).
{
  "admin": {},
  "argv": [
    "string"
  ],
  "command": "string",
  "cwd": "string",
  "env": {},
  "kill_on_disconnect": true,
  "shell": "default",
  "stdin": "string",
  "timeout_secs": 0
}
ApiE2eBody
e2eobjectrequired
A desk operation sealed to the desk's key (e2e_pub): X25519 with the caller's ephemeral key pub, HKDF-SHA256, XChaCha20-Poly1305 (protocol/src/e2e.rs; docs/api/README.md). Base64url, no padding.
Show child parameters
vinteger
pubstringrequired
The caller's ephemeral X25519 public key (32 bytes).
noncestringrequired
24 bytes.
ciphertextstringrequired
{
  "e2e": {
    "v": 0,
    "pub": "string",
    "nonce": "string",
    "ciphertext": "string"
  }
}

Code samples

cURL
curl -X POST "https://api.gaiadesk.net/v1/desks/{id}/exec" \
  -H "Authorization: Bearer $GAIADESK_API_KEY" \
  -H "GaiaDesk-E2E: <value>" \
  -H "Idempotency-Key: <value>" \
  -H "Content-Type: application/json" \
  -d '{"admin":{},"argv":["string"],"command":"string","cwd":"string","env":{},"kill_on_disconnect":true,"shell":"default","stdin":"string","timeout_secs":0}'
TypeScript
Python
Go
PHP
Ruby
Java

Responses

200 How it ended (or, with stream=1, the event stream).
application/json
Example
{
  "desk": "string",
  "duration_ms": 0,
  "error": {
    "desk": "string",
    "kind": "string",
    "message": "string",
    "reason": "string"
  },
  "exit": 0,
  "mode": "string",
  "notes": [
    "string"
  ],
  "remote_code": 0,
  "route": "string",
  "shell": "string",
  "stderr": "string",
  "stdout": "string",
  "timed_out": true,
  "truncated": true
}
ExecResult
deskstringrequired
duration_msintegerrequired
errorError
null when it ran and ended on its own.
Show child parameters
deskstring
The desk it concerned, when there was one.
kindstringrequired
The six kinds a program branches on. Stable: a new kind would be a breaking change, so match with a wildcard arm anyway.
messagestringrequired
reasonstring
The finer cause: offline, unknown_desk, not_online, network, not_signed_in, timeout, local, …
exitintegerrequired
What gaiadesk-cli exec exits with: the program's code (0-255), 124 timed out, 130 interrupted, 254 refused, 255 not reached.
modestring
pipes, or terminal on a desk from before plain pipes.
notesstring[]required
This client's own lines about the run.
remote_codeinteger
The program's own code (null: it never ran).
routestring
LAN, Mesh, the GaiaDesk server (null: never reached).
shellstring
The shell the desk actually used (null: it did not say).
stderrstringrequired
stdoutstringrequired
timed_outbooleanrequired
truncatedbooleanrequired
More than 16 MB of a stream was dropped.
{
  "desk": "string",
  "duration_ms": 0,
  "error": {
    "desk": "string",
    "kind": "string",
    "message": "string",
    "reason": "string"
  },
  "exit": 0,
  "mode": "string",
  "notes": [
    "string"
  ],
  "remote_code": 0,
  "route": "string",
  "shell": "string",
  "stderr": "string",
  "stdout": "string",
  "timed_out": true,
  "truncated": true
}
ApiE2eAnswer
e2eobjectrequired
Show child parameters
vintegerrequired
eventsApiE2eFrame[]required
Show child parameters
seqintegerrequired
noncestringrequired
ciphertextstringrequired
{
  "e2e": {
    "v": 0,
    "events": [
      {
        "seq": 0,
        "nonce": "string",
        "ciphertext": "string"
      }
    ]
  }
}
text/event-stream
{
  "data": "string",
  "event": "string"
}
Headers
X-Request-Idstring
This request's id (req_…), also in every error body. Quote it to support.
RateLimit-Limitinteger
Requests allowed per window for this key.
RateLimit-Remaininginteger
Requests left in the current window.
RateLimit-Resetinteger
Seconds until the window resets.
Idempotent-Replayedstring
true when this answer is a stored one, replayed for a repeated Idempotency-Key.
400 The request is malformed (usage).
Example
{
  "error": {
    "desk": "string",
    "kind": "string",
    "message": "string",
    "reason": "string",
    "request_id": "string",
    "status": 0
  },
  "e2e": {
    "v": 0,
    "events": [
      {
        "seq": 0,
        "nonce": "string",
        "ciphertext": "string"
      }
    ]
  }
}
Schema
errorError & objectrequired
The CLI's error object plus this request's id.
Show child parameters
deskstring
The desk it concerned, when there was one.
kindstringrequired
The six kinds a program branches on. Stable: a new kind would be a breaking change, so match with a wildcard arm anyway.
messagestringrequired
reasonstring
The finer cause: offline, unknown_desk, not_online, network, not_signed_in, timeout, local, …
request_idstringrequired
statusinteger
Only in a held body (GaiaDesk-Held) — the HTTP status this failure would have had, had its 200 not been sent already.
e2eobject
An end-to-end encrypted operation's events; the last opens to the desk's real error (its message here is a placeholder).
Show child parameters
vintegerrequired
eventsApiE2eFrame[]required
Show child parameters
seqintegerrequired
noncestringrequired
ciphertextstringrequired
401 No credential, or one that does not verify (refused, unauthenticated).

Same as 400 · POST /desks/{id}/exec

403 The credential lacks the scope, or may not use this API (refused).

Same as 400 · POST /desks/{id}/exec

404 No such desk or resource visible to the caller (unreachable, unknown_desk).

Same as 400 · POST /desks/{id}/exec

409 The desk is offline (unreachable, with the reach log's reason: silent, closed, …) or could not be woken within wake_s; or it runs a GaiaDesk from before desk operations (protocol, desk_too_old: update GaiaDesk on this desk).

Same as 400 · POST /desks/{id}/exec

429 Over this key's rate limit (rate_limited), or the desk already runs 16 API operations (desk_busy).

Same as 400 · POST /desks/{id}/exec

Headers
Retry-Afterinteger
Seconds to wait before trying again.
502 The desk went away during the operation (connection_lost), or answered something this server cannot read (protocol).

Same as 400 · POST /desks/{id}/exec

503 Desk operations are turned off on this server (unreachable, desk_ops_disabled).

Same as 400 · POST /desks/{id}/exec

default The error envelope.

Same as 400 · POST /desks/{id}/exec

GEThttps://api.gaiadesk.net/v1/desks/{id}/jobsScope jobs

The desk's background jobs.

Parameters

idstringpathrequired
The desk's nine-digit id.
GaiaDesk-E2Estringheader
End-to-end encrypted: the sealed request (ApiE2eRequest, as base64url of its JSON) in place of a plaintext body or parameters. The answer is then sealed too (ApiE2eAnswer, ApiE2eStreamEvent, ApiE2eFrame). A POST may instead send the body {"e2e": …} (ApiE2eBody). See the "End-to-end encryption" section of docs/api/README.md.
wake_sintegerquery
If the desk is asleep, ring it and wait up to this long (default 60, at most 120).

Code samples

cURL
curl -X GET "https://api.gaiadesk.net/v1/desks/{id}/jobs" \
  -H "Authorization: Bearer $GAIADESK_API_KEY" \
  -H "GaiaDesk-E2E: <value>"
TypeScript
Python
Go
PHP
Ruby
Java

Responses

200 The jobs.
Example
{
  "jobs": [
    {
      "by": "",
      "command": "string",
      "ended_at_ms": 0,
      "enforcement": [
        "string"
      ],
      "exit_code": 0,
      "limits": {
        "cpu_percent": 0,
        "keep_awake": true,
        "max_runtime_secs": 0,
        "mem_mb": 0,
        "priority": "string"
      },
      "log_bytes": 0,
      "max_running": 0,
      "name": "string",
      "pid": 0,
      "queue_position": 0,
      "reason": "string",
      "started_at_ms": 0,
      "state": "string"
    }
  ]
}
JobList
jobsJob[]required
Show child parameters
bystring
Who started it: the token's name, or owner for a person.
commandstringrequired
ended_at_msinteger
enforcementstring[]
How the desk enforces them, one line each, in its own words — and what it could not do here (e.g. "cgroups v2 not available: …").
exit_codeinteger
limitsJobLimits
The caps and keep-awake it runs under, as the desk applied them (keep_awake resolved against the desk's default). None from a desk before job limits, and for a job started with none.
Show child parameters
cpu_percentinteger
At most this share of the whole machine's CPU, 1–100.
keep_awakeboolean
Hold off idle sleep while the job runs. None: the desk's default.
max_runtime_secsinteger
Stop the job (its whole process tree) after this many seconds of running (run --max-runtime); it ends killed (max runtime exceeded).
mem_mbinteger
At most this much memory for the job and everything it starts, MB.
prioritystring
low, normal or high.
log_bytesinteger
Bytes of output kept (current log plus its one rotated predecessor).
= 0
max_runninginteger
The desk's limit on jobs running at once; a job past it waits in state queued. Absent from a desk before queueing.
namestringrequired
pidinteger
queue_positioninteger
1-based place in the queue while state is queued.
reasonstring
Why it ended as it did, as a machine reads it, when the desk knows: blocked_by_os_policy — Windows Smart App Control or WDAC refused to start a program it ran (its state then says so in words).
started_at_msintegerrequired
statestringrequired
queued (waiting for a free slot), running, exited, killed, or lost (it was running when the GaiaDesk host restarted and its end could not be observed).
{
  "jobs": [
    {
      "by": "",
      "command": "string",
      "ended_at_ms": 0,
      "enforcement": [
        "string"
      ],
      "exit_code": 0,
      "limits": {
        "cpu_percent": 0,
        "keep_awake": true,
        "max_runtime_secs": 0,
        "mem_mb": 0,
        "priority": "string"
      },
      "log_bytes": 0,
      "max_running": 0,
      "name": "string",
      "pid": 0,
      "queue_position": 0,
      "reason": "string",
      "started_at_ms": 0,
      "state": "string"
    }
  ]
}
ApiE2eAnswer
e2eobjectrequired
Show child parameters
vintegerrequired
eventsApiE2eFrame[]required
Show child parameters
seqintegerrequired
noncestringrequired
ciphertextstringrequired
{
  "e2e": {
    "v": 0,
    "events": [
      {
        "seq": 0,
        "nonce": "string",
        "ciphertext": "string"
      }
    ]
  }
}
Headers
X-Request-Idstring
This request's id (req_…), also in every error body. Quote it to support.
RateLimit-Limitinteger
Requests allowed per window for this key.
RateLimit-Remaininginteger
Requests left in the current window.
RateLimit-Resetinteger
Seconds until the window resets.
400 The request is malformed (usage).

Same as 400 · POST /desks/{id}/exec

401 No credential, or one that does not verify (refused, unauthenticated).

Same as 400 · POST /desks/{id}/exec

403 The credential lacks the scope, or may not use this API (refused).

Same as 400 · POST /desks/{id}/exec

404 No such desk or resource visible to the caller (unreachable, unknown_desk).

Same as 400 · POST /desks/{id}/exec

409 The desk is offline (unreachable, with the reach log's reason: silent, closed, …) or could not be woken within wake_s; or it runs a GaiaDesk from before desk operations (protocol, desk_too_old: update GaiaDesk on this desk).

Same as 400 · POST /desks/{id}/exec

429 Over this key's rate limit (rate_limited), or the desk already runs 16 API operations (desk_busy).

Same as 400 · POST /desks/{id}/exec

Headers
Retry-Afterinteger
Seconds to wait before trying again.
502 The desk went away during the operation (connection_lost), or answered something this server cannot read (protocol).

Same as 400 · POST /desks/{id}/exec

503 Desk operations are turned off on this server (unreachable, desk_ops_disabled).

Same as 400 · POST /desks/{id}/exec

default The error envelope.

Same as 400 · POST /desks/{id}/exec

Updated