desk-ops
https://api.gaiadesk.net/v1 /desks/{id}/exec Scope execRun 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
idstringpathrequiredGaiaDesk-E2EstringheaderApiE2eRequest, 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-KeystringheaderIdempotent-Replayed: true); the same key with a different request is refused (usage, idempotency_key_reused).wake_sintegerquerystreamintegerquery1: answer Server-Sent Events.Request body
application/json
Example
{
"admin": {},
"argv": [
"string"
],
"command": "string",
"cwd": "string",
"env": {},
"shell": "default",
"stdin": "string",
"timeout_secs": 0
}ApiExecSpec
adminadmin_not_via_api).argvstring[]commandstringcwdstringenvobjectshellstringstdinstringcrate::ExecInput] feeds it.timeout_secsnumber0: no limit; absent: 30 minutes).{
"admin": {},
"argv": [
"string"
],
"command": "string",
"cwd": "string",
"env": {},
"shell": "default",
"stdin": "string",
"timeout_secs": 0
}ApiE2eBody
e2eobjectrequirede2e_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
vintegerpubstringrequirednoncestringrequiredciphertextstringrequired{
"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":{},"shell":"default","stdin":"string","timeout_secs":0}'TypeScript
Python
Go
PHP
Ruby
Java
Responses
stream=1, the event stream).application/jsonExample
{
"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
deskstringrequiredduration_msintegerrequirederrorErrornull when it ran and ended on its own.Show child parameters
deskstringkindstringrequiredmessagestringrequiredreasonstringoffline, unknown_desk, not_online, network, not_signed_in, timeout, local, …exitintegerrequiredgaiadesk-cli exec exits with: the program's code (0-255), 124 timed out, 130 interrupted, 254 refused, 255 not reached.modestringpipes, or terminal on a desk from before plain pipes.notesstring[]requiredremote_codeintegernull: it never ran).routestringLAN, Mesh, the GaiaDesk server (null: never reached).shellstringnull: it did not say).stderrstringrequiredstdoutstringrequiredtimed_outbooleanrequiredtruncatedbooleanrequired{
"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
e2eobjectrequiredShow child parameters
vintegerrequiredeventsApiE2eFrame[]requiredShow child parameters
seqintegerrequirednoncestringrequiredciphertextstringrequired{
"e2e": {
"v": 0,
"events": [
{
"seq": 0,
"nonce": "string",
"ciphertext": "string"
}
]
}
}text/event-stream{
"data": "string",
"event": "string"
}X-Request-Idstringreq_…), also in every error body. Quote it to support.RateLimit-LimitintegerRateLimit-RemainingintegerRateLimit-ResetintegerIdempotent-Replayedstringtrue when this answer is a stored one, replayed for a repeated Idempotency-Key.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 & objectrequiredShow child parameters
deskstringkindstringrequiredmessagestringrequiredreasonstringoffline, unknown_desk, not_online, network, not_signed_in, timeout, local, …request_idstringrequiredstatusintegerGaiaDesk-Held) — the HTTP status this failure would have had, had its 200 not been sent already.e2eobjectmessage here is a placeholder).Show child parameters
vintegerrequiredeventsApiE2eFrame[]requiredShow child parameters
seqintegerrequirednoncestringrequiredciphertextstringrequiredrefused, unauthenticated).Same as 400 · POST /desks/{id}/exec
refused).Same as 400 · POST /desks/{id}/exec
unreachable, unknown_desk).Same as 400 · POST /desks/{id}/exec
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
rate_limited), or the desk already runs 16 API operations (desk_busy).Same as 400 · POST /desks/{id}/exec
Retry-Afterintegerconnection_lost), or answered something this server cannot read (protocol).Same as 400 · POST /desks/{id}/exec
unreachable, desk_ops_disabled).Same as 400 · POST /desks/{id}/exec
https://api.gaiadesk.net/v1 /desks/{id}/jobs Scope jobsThe desk's background jobs.
Parameters
idstringpathrequiredGaiaDesk-E2EstringheaderApiE2eRequest, 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_sintegerqueryCode 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
Example
{
"jobs": [
{
"by": "",
"command": "string",
"ended_at_ms": 0,
"enforcement": [
"string"
],
"exit_code": 0,
"limits": {
"cpu_percent": 0,
"keep_awake": true,
"mem_mb": 0,
"priority": "string"
},
"log_bytes": 0,
"name": "string",
"pid": 0,
"reason": "string",
"started_at_ms": 0,
"state": "string"
}
]
}JobList
jobsJob[]requiredShow child parameters
bystringowner for a person.commandstringrequiredended_at_msintegerenforcementstring[]exit_codeintegerlimitsJobLimitskeep_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_percentintegerkeep_awakebooleanNone: the desk's default.mem_mbintegerprioritystringlow, normal or high.log_bytesintegernamestringrequiredpidintegerreasonstringblocked_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_msintegerrequiredstatestringrequiredrunning, 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,
"mem_mb": 0,
"priority": "string"
},
"log_bytes": 0,
"name": "string",
"pid": 0,
"reason": "string",
"started_at_ms": 0,
"state": "string"
}
]
}ApiE2eAnswer
e2eobjectrequiredShow child parameters
vintegerrequiredeventsApiE2eFrame[]requiredShow child parameters
seqintegerrequirednoncestringrequiredciphertextstringrequired{
"e2e": {
"v": 0,
"events": [
{
"seq": 0,
"nonce": "string",
"ciphertext": "string"
}
]
}
}X-Request-Idstringreq_…), also in every error body. Quote it to support.RateLimit-LimitintegerRateLimit-RemainingintegerRateLimit-Resetintegerrefused, unauthenticated).Same as 400 · POST /desks/{id}/exec
refused).Same as 400 · POST /desks/{id}/exec
unreachable, unknown_desk).Same as 400 · POST /desks/{id}/exec
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
rate_limited), or the desk already runs 16 API operations (desk_busy).Same as 400 · POST /desks/{id}/exec
Retry-Afterintegerconnection_lost), or answered something this server cannot read (protocol).Same as 400 · POST /desks/{id}/exec
unreachable, desk_ops_disabled).Same as 400 · POST /desks/{id}/exec