desk-ops
https://api.gaiadesk.net/v1 /desks/{id}/jobs/{name}/logs Scope jobsA job and the end of its output; `?follow=1` streams it over SSE.
With follow=1: Server-Sent Events output, then end (the job as it ended), interrupted (following stopped; the job goes on) or error, each event's data one JobLogEvent object. A failure after the stream started is its last event, error (no job by that name, the desk lost, …), and the call's audit event records that failure, not the 200. Output buffered for a JSON answer is capped at 8 MB.
Parameters
idstringpathrequirednamestringpathrequiredGaiaDesk-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_sintegerqueryfollowintegerquerytailintegerqueryCode samples
cURL
curl -X GET "https://api.gaiadesk.net/v1/desks/{id}/jobs/{name}/logs" \
-H "Authorization: Bearer $GAIADESK_API_KEY" \
-H "GaiaDesk-E2E: <value>"TypeScript
Python
Go
PHP
Ruby
Java
Responses
follow=1, the event stream).application/jsonExample
{
"job": {
"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"
},
"output": "string"
}JobLogs
jobobjectrequiredShow 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.max_runtime_secsintegerrun --max-runtime); it ends killed (max runtime exceeded).mem_mbintegerprioritystringlow, normal or high.log_bytesintegermax_runningintegerqueued. Absent from a desk before queueing.namestringrequiredpidintegerqueue_positionintegerstate is queued.reasonstringblocked_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_msintegerrequiredstatestringrequiredqueued (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).outputstringrequired{
"job": {
"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"
},
"output": "string"
}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-Resetintegerusage).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).refused).unreachable, unknown_desk).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).rate_limited), or the desk already runs 16 API operations (desk_busy).Same as 400 · GET /desks/{id}/jobs/{name}/logs
Retry-Afterintegerconnection_lost), or answered something this server cannot read (protocol).unreachable, desk_ops_disabled).https://api.gaiadesk.net/v1 /desks/{id}/jobs/{name}/wait Scope jobsBlock until a job is no longer running, as `gaiadesk-cli wait` does.
Answers the job as it ended (timed_out: false) or, when timeout ran out first, as it stands, still running (timed_out: true). The job object is what gaiadesk-cli wait --json prints. No such job answers 422 failed, as the CLI does.
A wait is held at most 870 seconds, under the 15-minute limit of every call; wait again for longer. Keep-alive aware: an answer that takes longer than a moment (1.5 s) starts its 200 at once, with GaiaDesk-Held: 1, and holds the body open, sending one space every 15 seconds (whitespace before a JSON value is still JSON) until the outcome, so proxies never see a silent connection. A status cannot change once sent, so a held body is either the result ({"job", "timed_out"}) or, should the desk fail after that point, the error envelope ({"error"}, with error.status the status it would have had): read the body, and treat error as the failure. An answer within the first moments is an ordinary one, its failures real HTTP errors. Either way, the call's audit event records the outcome (ok, failed, …, or cancelled when the caller hung up first), not the 200.
Why not 202 and polling, or 102 Processing: a job is already the resource a caller polls (timeout=0 answers at once), so a 202 would add a second resource for nothing; 102 is a WebDAV interim response that fetch, most HTTP clients and proxies never surface; and trailers are dropped by browsers and most proxies. The held 200 is one request, with the outcome always in a body every client reads.
Parameters
idstringpathrequirednamestringpathrequiredGaiaDesk-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_sintegerquerytimeoutintegerquery0 answers at once).Code samples
cURL
curl -X GET "https://api.gaiadesk.net/v1/desks/{id}/jobs/{name}/wait" \
-H "Authorization: Bearer $GAIADESK_API_KEY" \
-H "GaiaDesk-E2E: <value>"TypeScript
Python
Go
PHP
Ruby
Java
Responses
timeout ran out — or, only on a held answer (GaiaDesk-Held: 1), the error envelope of a failure after the 200 was sent.Example
{
"job": {
"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"
},
"timed_out": true
}JobWaitResult
jobobjectrequiredShow 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.max_runtime_secsintegerrun --max-runtime); it ends killed (max runtime exceeded).mem_mbintegerprioritystringlow, normal or high.log_bytesintegermax_runningintegerqueued. Absent from a desk before queueing.namestringrequiredpidintegerqueue_positionintegerstate is queued.reasonstringblocked_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_msintegerrequiredstatestringrequiredqueued (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).timed_outbooleanrequired{
"job": {
"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"
},
"timed_out": true
}ApiErrorEnvelope
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
seqintegerrequirednoncestringrequiredciphertextstringrequired{
"error": {
"desk": "string",
"kind": "string",
"message": "string",
"reason": "string",
"request_id": "string",
"status": 0
},
"e2e": {
"v": 0,
"events": [
{
"seq": 0,
"nonce": "string",
"ciphertext": "string"
}
]
}
}ApiE2eAnswer
e2eobjectrequiredShow child parameters
vintegerrequiredeventsApiE2eFrame[]requiredShow child parameters
seqintegerrequirednoncestringrequiredciphertextstringrequired{
"e2e": {
"v": 0,
"events": [
{
"seq": 0,
"nonce": "string",
"ciphertext": "string"
}
]
}
}GaiaDesk-Heldstring1: this 200 was sent before its outcome was known, and the body was held open (keep-alive whitespace) until it was. The body is the result object or the error envelope, never both: check for error.X-Request-Idstringreq_…), also in every error body. Quote it to support.RateLimit-LimitintegerRateLimit-RemainingintegerRateLimit-Resetintegerrefused, unauthenticated).refused).unreachable, unknown_desk).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).rate_limited), or the desk already runs 16 API operations (desk_busy).Same as 400 · GET /desks/{id}/jobs/{name}/logs
Retry-Afterintegerconnection_lost), or answered something this server cannot read (protocol).unreachable, desk_ops_disabled).