Control

Run a command

Runs a command inside the guest and waits for it, up to timeout_s.

On the hosted API a foreground request is cut off at the edge after about 120 seconds, whatever timeout_s says (a 524). The command keeps running, and until timeout_s runs out — or the command ends, if that is sooner — it holds the computer’s guest agent, so another exec, a clipboard, window or file call on that computer can answer 409 in that time, and so can input that goes through the guest agent: a paste, or a type whose text is not plain ASCII. Screenshots, pointer and key input, and a type of plain ASCII text do not wait on it. Use background: true for anything that may take more than about 100 seconds.

Pass background: true for a command that outlives the request: the response is a handle carrying the guest pid, and you read its output with GET /computers/{id}/exec/{pid} and stop it with the DELETE. timeout_s means nothing alongside it, because not waiting is the whole request. One computer may hold at most 16 background commands. A seventeenth answers 409 without a reason: the existing commands may be long-lived servers, so retrying does not necessarily help; stop one of the handles you already hold first.

A stopped computer answers 409 with reason: "unavailable", a foreground command or a background one: start it first. Waiting does not clear it. A computer that stops while its command is starting or running answers the same, and that command may have run in part, so decide for yourself whether to send it again. A computer being moved to another host answers 409 with reason: "contention" instead, and the same request works once the move finishes. A command the move itself cuts off answers 409 unavailable, as any other stop does: it may have run in part, so it is not one to send again without deciding to.

A suspended computer is resumed to run the command — after session, env and cwd have been checked, so a request refused for one of them, or for asking a Windows guest for a desktop or background command, never resumes or bills the computer.

Explicit retain_output:true or a finite options object captures only the already-returned completed synchronous response. Missing/false performs no retained storage work. The option is public/internal only; it does not open session exec. Effective background:true with retention, duplicate canonical options, or trailing non-whitespace after an enabled first object is invalid_retain_output 400 before dispatch. Original request bytes are forwarded unchanged. Optional admission adds at most 1000 milliseconds and publication at most 5000 milliseconds; inspection is bounded at 50331648 response bytes. Only successful immutable publication adds result_id. Every optional failure preserves the original command status/body with X-GC-Retained-Output: unavailable. Missing/false adds no header. Original credential loss takes precedence over private delivery; a refused or lost response does not prove the command did not run. Never replay exec to repair retention.

Requires the member role or higher.

post/computers/{id}/exec

Request

  • Base URL: https://app.mandala.computer/api/v1
  • URL: https://app.mandala.computer/api/v1/computers/{id}/exec
  • Auth: HTTP bearer

Path parameters

idstring required

The computer id.

Request body

commandstring required

The command line.

sessionstring

Empty runs as the guest agent’s own account — root, or SYSTEM on Windows. desktop runs inside the console user’s graphical session, which is where a GUI application has to start: launch one any other way and it has no display to appear on.

desktop is refused outright on Windows guests — there is no way to reach the interactive session there yet — and the refusal carries reason: "unsupported", the word for something no action on the computer changes. It is answered before the computer is asked whether it is running, so a stopped Windows computer tells you this rather than telling you to start it first. Empty session is unaffected: an ordinary command runs on Windows through cmd.exe.

On Linux, a computer made from an older image has a shell profile hook that would run its bound secrets as code, and the platform replaces it before the first desktop command. If that replacement fails (a full or read-only guest disk, say), desktop answers 503 with no reason and nothing is run; the next request tries the replacement again. An empty session runs as root, which that hook skips, and is never refused for it.

timeout_sinteger

Seconds to wait. Defaults to 30, and 600 is the most it accepts — the wait holds the computer’s guest agent, so no other call that needs it reaches that machine for its length, and a higher value is a 400 rather than a quietly shorter wait. Send background: true for anything longer. Ignored when background is set. On the hosted API the edge cuts a foreground request off after about 120 seconds whatever this says. The command keeps running and holds the guest agent until this runs out, or until it ends if that is sooner, so exec, clipboard, window and file calls on that computer can answer 409 until then, as can a paste input or a type whose text is not plain ASCII: use background: true for anything that may take more than about 100 seconds.

backgroundboolean

Answer with a handle instead of a result. Refused on Windows guests with 400 and reason: "unsupported", before the computer is resumed.

cwdstring

Working directory inside the guest: an absolute path, or the request is a 400. If the shell cannot enter it, no part of the command runs and exit_code is 127, on Linux and on Windows alike. On Windows a UNC path, or one containing ", % or !, is also a 400, and the command runs under cmd.exe as (cd /d "<cwd>" || exit 127) & <command>, so a compound command is parsed as it would be on its own. cmd.exe expands %VAR% across that whole line before any of it runs, so a %CD% in the command reads the directory cmd.exe started in, not cwd. Whether a Windows guest enters cwd this way has not yet been measured: if it does not, every such command exits 127 without running. A 127 alone does not prove that, since a command that ran can exit 127 too.

envobject

Environment variables. At most 64 entries, each at most 4096 bytes as NAME=value — the name, the value and the = between them count together, so a long value leaves less room for a long name. Past either is a 400, and so are an empty name, an = inside a name, and a NUL in either half — the last two because the guest agent would carry them silently rather than refuse. The per-entry refusals name the entry they mean; going over the COUNT reports the two counts, since there is no one entry at fault. Nothing partial runs. These bound what the guest agent is asked to spawn, so they apply whether or not background is set.

What these are added TO is worth saying, because it is not what the guest agent was started with. On Linux your command runs through a login shell, so the base is whatever the guest user’s profile sets — PATH included — and your entries go on top of that. A variable the guest agent happened to hold and the profile does not set is not there to inherit. On Windows a command runs through cmd.exe, which sources no profile, so there is no such base; background is refused there outright.

Secrets bound to the computer as environment variables are in that base too, on an image that supports it, for every command — a plain one run as root as well as one with session: "desktop" — at their values when the command starts, so a replaced value applies to the next command. Windows computers cannot be bound to secrets.

An entry here wins over both: over a bound value of the same name, and over whatever a login profile sets while the shell starts (a template’s defaults, PATH), because your entries are applied again, as plain strings, after the profiles and the bound values, just before your command. Not applied again: a name a shell cannot assign or that it uses itself (such as PS4, or anything beginning BASH_), which keeps what the shell made of it; a variable a profile made read-only; and, with session: "desktop", the session’s own DISPLAY, XAUTHORITY, DBUS_SESSION_BUS_ADDRESS, XDG_RUNTIME_DIR, WAYLAND_DISPLAY, HYPRLAND_INSTANCE_SIGNATURE, HOME, USER and LOGNAME, which stay the session’s: a session that has one keeps its value, and one it does not have is set only by your entry, if you give one, and otherwise unset.

Your entries are ALSO in the environment the login profiles run under, from the moment the shell starts: they are applied again after the profiles, not instead of being there during them. So overriding PATH, or anything else a profile reads, changes what the profiles do, and can break one; a profile that fails that way adds its errors to the command’s stderr.

Response

The command finished inside timeout_s. This is its result.

result_idstring

Optional committed synchronous retained version, present only after explicit retain_output capture succeeds. Never an execution ID or replay key.

exit_codeinteger required
stdout_b64string required

What the command printed on stdout, base64. Decode it: echo "$STDOUT_B64" | base64 -d, or your language’s own decoder.

Base64 rather than text because a JSON string is UTF-8 by definition and a command’s output is bytes. A field carrying the bytes directly could only carry the ones that happen to be valid UTF-8, and everything else — a tarball, a PNG, a latin-1 build log — arrived silently rewritten, byte by byte, into U+FFFD. There is no flag for that and there could not be a useful one: half a response would be intact and half replaced, with nothing to say which.

stderr_b64string required

The same, for stderr.

timed_outboolean required

The command hit timeout_s and was not waited for any longer. Check this BEFORE reading exit_code — a timed-out command has not reported one, and exit_code then reads -1, a sentinel and not the command’s status.

out_truncatedboolean required

The guest agent stopped capturing stdout before the command stopped producing it, at 16 MiB. What you have is a prefix, and nothing else in the response would tell you so.

err_truncatedboolean required

The same, for stderr.

Changes

No recorded changes to this operation across all 1 revision of this API.