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}/jobsScope jobs

Start a background job (the same fields as `gaiadesk-cli run --detach`).

It runs under the desk's GaiaDesk host and outlives this request; its end is the job.finished webhook.

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.

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).

Request body

application/json
Example
{
  "command": [
    "string"
  ],
  "cwd": "string",
  "env": {},
  "limits": {},
  "name": "string",
  "shell": "string"
}
JobSpec
commandstring[]required
ONE entry is the command line, verbatim for the desk's shell (sh -c, cmd /c); several are words, each quoted for THAT desk's shell.
cwdstring
envobject
Environment variables for it (never logged).
limitsobject
What a background job may use, and whether the desk stays awake for it ([super::DataMessage::JobStart]'s limits). Every field optional and off the wire when unset, so a start without limits is byte for byte what it was. A desk lists [super::feature::JOB_LIMITS] when it enforces them; a desk before that ignores the field, which is why a CLI refuses to send caps to one.
= {}
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.
namestringrequired
Letters, digits, ., _, -: how jobs, job_logs and kill_job find it.
shellShell
The shell that runs it (absent: the desk's job shell — sh -c, cmd /c). none is not one: a job is a command line.
{
  "command": [
    "string"
  ],
  "cwd": "string",
  "env": {},
  "limits": {},
  "name": "string",
  "shell": "string"
}
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}/jobs" \
  -H "Authorization: Bearer $GAIADESK_API_KEY" \
  -H "GaiaDesk-E2E: <value>" \
  -H "Idempotency-Key: <value>" \
  -H "Content-Type: application/json" \
  -d '{"command":["string"],"cwd":"string","env":{},"limits":{},"name":"string","shell":"string"}'
TypeScript
Python
Go
PHP
Ruby
Java

Responses

201 Started.
Example
{
  "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"
}
Job
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).
{
  "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.
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}/jobs

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

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

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

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

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}/jobs

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}/jobs

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}/jobs

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

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

default The error envelope.

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

DELETEhttps://api.gaiadesk.net/v1/desks/{id}/jobs/{name}Scope jobs

Stop a job and everything it started.

Parameters

idstringpathrequired
The desk's nine-digit id.
namestringpathrequired
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 DELETE "https://api.gaiadesk.net/v1/desks/{id}/jobs/{name}" \
  -H "Authorization: Bearer $GAIADESK_API_KEY" \
  -H "GaiaDesk-E2E: <value>"
TypeScript
Python
Go
PHP
Ruby
Java

Responses

200 Stopped.

Same as 201 · POST /desks/{id}/jobs

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}/jobs

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

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

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

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

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

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

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}/jobs

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}/jobs

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}/jobs

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

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

default The error envelope.

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

Updated