Skip to content
Hoody.com

The terminal API executes commands in PTY-backed sessions inside a container and retrieves their output. Use these endpoints to run one-off shell commands, poll for results, write interactive keystrokes (for prompts and password entry), and cancel long-running commands.

All endpoints route through the terminal service running inside the container. The base URL follows the pattern https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com. Callers can address a specific terminal session through the terminal-N proxy label, or pass a terminal_id query parameter directly when calling the service without the proxy.

These endpoints belong to the terminal:Terminal Execution service and are mounted under client.terminal.commands.* in the Hoody SDK: client.terminal.commands.run(...) executes a command and client.terminal.commands.get(...) reads its result.

Start a command in a terminal session. Supports both local bash and remote SSH sessions; the terminal type is determined on first use based on URL parameters. By default, if a DISPLAY is configured on the session, the endpoint waits for the Hoody Display to be ready before running the command. The wait is skipped when no X server holds that display and none can be started (no xpra installed, or a display above 40000, and no startup script that could start it), since it could only time out; disable it yourself with skip_display_wait=true. Use ephemeral=true for a guaranteed-unique isolated PTY session with no display/dbus and automatic cleanup.

On a local session the command is handed to the shell as a file, so any text runs as written (a !, an unbalanced quote or an open heredoc is the command’s own business: bash reports a syntax error as exit code 2 and the session stays usable), and stdout holds only what the command printed (no prompt, banner or earlier output). On an SSH session the command is typed into the remote shell. With wait=true (the default) the response is the finished result, the same object GET /api/v1/terminal/result/{command_id} returns; with wait=false it returns at once with the command_id and status running (queued with defer_pid), and the result is polled from /result. The terminal is a PTY: stderr is merged into stdout, line endings are CRLF, escape sequences are removed and leading/trailing whitespace is trimmed. exit_code is the command’s own exit status when completion is marker; see completion for the other cases.

For the command’s exact bytes, send "mode": "raw" in the body. The command then runs as bash -c (/bin/sh -c when bash is not installed; shell in the response says which) in a process of its own, without a terminal and without a terminal session: terminal_id, ephemeral and the other session parameters are ignored, and an SSH parameter is a 400. stdin is /dev/null, or the decoded stdin_b64. stdout and stderr come back apart and unchanged in stdout_b64 and stderr_b64 (binary data, bare LF, whitespace, escape sequences), each up to the server’s capture limit; the rest is read and dropped, with stdout_truncated / stderr_truncated set. The response is sent once the command has exited and stdout and stderr are closed, so a background process that keeps them open holds it until the timeout; redirect its output to let the command return. At the timeout, and when the client disconnects, the command’s whole process group gets SIGTERM, then SIGKILL after 3 s. Raw mode needs wait=true, and its command_id is not known to /result or /abort.

NameInTypeRequiredDescription
terminal_idquerystringNoTerminal session ID (numeric 1-65535). Required unless ephemeral=true, in which case it is auto-generated if not provided. On calls routed through a terminal-N containers-proxy hostname, the proxy sets this from the hostname label and overwrites any value you send. A ?terminal_id=0 query sentinel never survives the proxy. Use the terminal-0 hostname as the no-id sentinel so ephemeral=true can auto-generate; supply this parameter directly only when calling the terminal service without the proxy.
ephemeralquerybooleanNoWhen true, auto-generates a unique terminal_id (if not provided), skips display/dbus initialization, and applies aggressive cleanup. Designed for programmatic CLI command execution like a scripted command runner (default: false). Do NOT use ephemeral=true for GUI applications that require a display. Ephemeral sessions strip the DISPLAY environment variable, which means X11/GUI applications will not work. Use a regular terminal session with an explicit terminal_id and display parameter instead for GUI workloads.
defer_pidqueryintegerNoDefer command injection until this PID exits (TUI-safe). If set, the API returns immediately regardless of wait=true.
defer_start_time_ticksquerystringNoOptional /proc/<pid>/stat field 22 (starttime in clock ticks since boot) to avoid PID reuse bugs. If it mismatches, command executes immediately.
defer_timeout_msqueryintegerNoMax time to wait for defer_pid exit before failing (default: 60000).
defer_poll_msqueryintegerNoPoll interval while waiting for defer_pid exit (default: 50, minimum: 10).
resetquerybooleanNoReset existing session and reconfigure (kills current process, clears state, allows switching from bash to SSH or changing any parameter). Use true, 1, or no value.
cwdquerystringNoWorking directory for local bash sessions (ignored for SSH). In raw mode: the command’s working directory, when the body has no cwd.
cwd_auto_createquerybooleanNoAuto-create cwd when the requested working directory does not exist yet. Only applies when cwd is explicitly provided for a new or reset local session. Enable with true, 1, or no value (default: false).
shellquerystringNoShell to use for local sessions: bash (case-insensitive), zsh, fish, sh, etc. (default: server startup command, only applies to new sessions or after reset).
userquerystringNoSystem user to spawn shell as (requires su permissions, only applies to new sessions or after reset). In raw mode: the user the command runs as, when the body has no user.
cmdquerystringNoBase64-encoded command to execute automatically (works with both new and active shells, executes every time the URL is visited).
envquerystringNoEnvironment variable in KEY=VALUE format. Can be repeated for multiple variables, e.g., ?env=DEBUG=1&env=API_KEY=abc.
skip_display_waitquerybooleanNoSkip waiting for Hoody Display readiness before executing command. By default, if a DISPLAY is configured, the request waits until the session’s display server is ready, unless no X server holds the display and none can be started, or a wait for it already timed out on the same shell within the last 30 seconds. Commands of one terminal still run in arrival order, so a request with skip_display_wait=true runs after earlier ones still waiting (default: false).
display_wait_timeoutqueryintegerNoTimeout in seconds for display readiness wait, counted from the request (default: 10, capped at 10 seconds; values <=0 or malformed also map to the 10-second cap). When it elapses the command runs anyway. Ignored if skip_display_wait=true.
displayquerystringNoDISPLAY environment variable for X11 applications. Auto-formats :display if a number is provided, e.g., ?display=1 becomes DISPLAY=:1.
ssh_hostquerystringNoSSH server hostname or IP address. Creates an SSH session if provided with ssh_user.
ssh_userquerystringNoSSH username. Required if ssh_host is provided.
ssh_portquerystringNoSSH port number (default: 22).
ssh_passwordquerystringNoSSH password for authentication. Use with caution; prefer key-based authentication.
socks5_hostquerystringNoSOCKS5 proxy hostname for SSH connection.
socks5_portquerystringNoSOCKS5 proxy port (default: 1080).
socks5_userquerystringNoSOCKS5 proxy username for authentication.
ssh_keyquerystringNoBase64-encoded SSH private key for key-based authentication. Prefer over password-based authentication.
socks5_passquerystringNoSOCKS5 proxy password for authentication.
NameTypeRequiredDescription
commandstringYesThe command to execute. In raw mode at most 131071 bytes.
idstringNoCustom command ID (numeric 1-65535, auto-generated if not provided). Ignored in raw mode.
timeoutintegerNoTimeout in seconds. 0 or omitted means no timeout on a persistent session and 600 seconds on an ephemeral one. It counts from the request, also while the command waits for an earlier one on the same session; a command still waiting when it elapses is never run. When it elapses the result is status timed_out with exit_code 124; on a persistent session a command that was running keeps running in the shell (commands behind it wait until it has finished); on an ephemeral one the command’s job and the shell get SIGTERM, then SIGKILL after 3 s. In raw mode 0 or omitted means the server’s default (600 seconds); at the timeout the command’s process group gets SIGTERM, then SIGKILL after 3 s, and the result is status timed_out with completion timeout.
waitbooleanNoWhether to wait for completion (default: true; forced false when defer_pid is set). Raw mode refuses false.
modestringNopty (default): run in the terminal session and return cleaned terminal text. raw: run as a one-shot process on pipes and return the exact bytes in stdout_b64 and stderr_b64.
stdin_b64stringNoRaw mode only: base64 of the bytes to write to the command’s stdin, which is then closed. Without it stdin is /dev/null.
userstringNoRaw mode only: system user to run the command as (default: the user query parameter, else the server’s default user). Switching users goes through sudo -n, or su where sudo is not installed.
cwdstringNoWorking directory for command execution (for local bash only). The session stays in that directory afterwards.
envobjectNoEnvironment variables for this command only, as string values. On a local session they do not persist into later commands, also after an abort, and a shadowed variable gets its earlier value back (on bash with its attributes); a cd or export done by the command itself does persist. Keys must be shell variable names ([A-Za-z_][A-Za-z0-9_]*) not starting with __HOODY_ (any case), else 400. On an SSH session they are exported in the remote shell and persist.
Terminal window
curl -X POST 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/execute?terminal_id=12345' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"command":"ls -la","timeout":30,"wait":true}'

Raw mode, returning the command’s exact bytes:

Terminal window
curl -X POST 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/execute' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"command":"tar -cz .","mode":"raw","cwd":"/etc/ssl"}' | jq -r .stdout_b64 | base64 -d > ssl.tgz

With wait=true, the finished command result (the /result/{command_id} object, fields below); with wait=false, the started command (status running or queued) plus session metadata and a human-readable message.

In raw mode the result carries mode (raw), command_id (raw-N, a label only), status (completed or timed_out), completion (exited or timeout), exit_code (null when a signal ended the command), signal (the signal’s name, such as SIGKILL, else null), timed_out, stdout_b64, stderr_b64, stdout_truncated, stderr_truncated, duration_ms (from the command’s start to its end), pid, shell, user and cwd, and no terminal_id:

{
"mode": "raw",
"command_id": "raw-1",
"status": "completed",
"completion": "exited",
"exit_code": 0,
"signal": null,
"timed_out": false,
"stdout_b64": "aGVsbG8K",
"stderr_b64": "",
"stdout_truncated": false,
"stderr_truncated": false,
"duration_ms": 12,
"pid": 4242,
"shell": "/usr/bin/bash",
"user": "user",
"cwd": "/home/user"
}

The pty-mode result:

{
"terminal_id": "12345",
"command_id": "45678",
"command": "ls -la",
"status": "completed",
"stdout": "total 0",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"exit_code": 0,
"completion": "marker",
"timed_out": false,
"cancelled": false,
"started": true,
"ephemeral": false,
"pid": 4242,
"shell_pid": 4243,
"cwd": "/home/user",
"created_at": 1698765432,
"shell": "bash",
"user": "user",
"home": "/home/user"
}

Retrieve the current or final results of a command execution. Can be called while the command is running or after completion.

NameInTypeRequiredDescription
command_idpathstringYesCommand ID returned from /api/v1/terminal/execute (numeric 1-65535).
Terminal window
curl 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/result/45678' \
-H 'Authorization: Bearer <token>'

Command results (running or completed). The terminal is a PTY, so stderr is always empty (stderr is merged into stdout) and line endings are CRLF.

{
"terminal_id": "12345",
"command_id": "45678",
"command": "ls -la",
"status": "completed",
"stdout": "file1\r\nfile2",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"exit_code": 0,
"completion": "marker",
"timed_out": false,
"cancelled": false,
"started": true,
"ephemeral": false,
"pid": 4242,
"shell_pid": 4243,
"cwd": "/home/user",
"created_at": 1698765432,
"shell": "bash",
"user": "user",
"home": "/home/user"
}
FieldTypeDescription
terminal_idstringTerminal session ID the command ran in
command_idstringCommand ID
commandstringThe command as sent
statusstringqueued, running, completed, timed_out or cancelled
stdoutstringWhat the command printed so far, stderr included, NUL bytes kept
stderrstringAlways empty: a PTY merges stderr into stdout
stdout_truncatedbooleanstdout was cut at the server’s capture limit
stderr_truncatedbooleanMirrors stdout_truncated for stderr
exit_codeinteger | nullThe command’s exit status when completion is marker; 124 on timeout; 130 or 137 when aborted; the process status when the session’s process ended. Null while running and when the status is unknown (completion output_quiet)
completionstring | nullHow the end was determined: marker (the shell reported the command’s own exit status), timeout, cancelled, output_quiet (no completion marker and 10 s without output; exit status unknown, the command may still be running) or ended (the session’s process exited, or the command could not be delivered). Null while running or queued
timed_outbooleanThe timeout elapsed
cancelledbooleanThe command was aborted
startedbooleanThe shell began running the command (its start marker was seen; on an SSH session: the command was typed). False while it waits behind an earlier command, when it ended while waiting (it never ran), and for a typed command the shell never started
ephemeralbooleanThe session is ephemeral (absent once the session is gone)
pidintegerPID of the session’s PTY process
shell_pidinteger | nullPID of the session’s shell, when found
cwdstring | nullThe shell’s working directory
created_atintegerSession creation time (Unix seconds)
shellstringThe session’s shell
userstring | nullThe user the session’s shell runs as
homestring | nullThe shell’s HOME

These endpoints belong to the terminal:Terminal service and are in the Hoody SDK as client.terminal.sessions.write(...) (send input to a terminal) and client.terminal.commands.cancel(...) (stop a running command).

Send keyboard input to a terminal session’s PTY. The input is written directly to the PTY master fd, exactly as if typed on a physical keyboard. By default, Enter (newline) is appended after the input; set enter=false for raw input. Use empty input "" to just press Enter. Supports interactive prompts (y/n), sudo passwords, and any other stdin input.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID to write to. On calls routed through a terminal-N containers-proxy hostname, the proxy sets this from the hostname label and overwrites any value you send. Pick the terminal via the hostname; supply it directly only when calling the terminal service without the proxy.
NameTypeRequiredDescription
inputstringYesThe text to type into the terminal.
enterbooleanNoAuto-append Enter (newline) after input. Default: true. Set to false for raw keystroke input.
Terminal window
curl -X POST 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/write?terminal_id=40001' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"input":"y","enter":true}'

Input written successfully.

{
"success": true,
"terminal_id": "40001",
"bytes_written": 2
}

POST /api/v1/terminal/execute/{command_id}/abort

Section titled “POST /api/v1/terminal/execute/{command_id}/abort”

Cancel a command that was started via the execute endpoint. Graceful mode (default) sends SIGINT via the PTY, equivalent to Ctrl+C. Force mode sends SIGKILL to the process group. Partial output captured before abort is preserved in the response. A command still waiting for an earlier one on its session (or for defer_pid) is cancelled without sending anything; it never runs.

NameInTypeRequiredDescription
command_idpathstringYesThe command ID returned by the execute endpoint.
NameTypeRequiredDescription
forcebooleanNoSend SIGKILL to process group instead of SIGINT (default: false).
Terminal window
curl -X POST 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/execute/45678/abort' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"force":false}'

Command aborted successfully.

{
"command_id": "12345",
"terminal_id": "1",
"status": "cancelled",
"stdout": "partial...",
"exit_code": 130,
"cancelled": true,
"message": "Command aborted successfully"
}