Terminal: Command Execution
Section titled “Terminal: Command Execution”Execute shell commands inside a container, poll for their results, abort running commands, and send interactive input to a terminal session. The execute endpoint starts a command and returns a command_id you can poll with the result endpoint, cancel with the abort endpoint, or feed interactive responses to with the write endpoint.
Execute a command
Section titled “Execute a command”POST /api/v1/terminal/execute
Section titled “POST /api/v1/terminal/execute”Execute a command in the specified terminal session. Supports both local bash and remote SSH sessions. The terminal type is determined by URL parameters on first use. By default, if a DISPLAY is configured on the session, the endpoint waits for the Hoody Display to be ready before executing the command. This can be disabled with skip_display_wait=true. Use ephemeral=true for a guaranteed-unique isolated PTY session with no display/dbus and automatic cleanup. Returns immediately with a command_id that can be used to poll for results.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | No | Terminal session ID (numeric 1-65535). Use terminal_id=0 as an explicit sentinel meaning “no terminal ID” (treated as absent, useful when a reverse proxy always injects a terminal_id). Required unless ephemeral=true, in which case it is auto-generated if not provided |
ephemeral | query | boolean | No | When 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 child_process.exec (default: false) |
defer_pid | query | integer | No | Defer command injection until this PID exits (TUI-safe). If set, the API returns immediately regardless of wait=true |
defer_start_time_ticks | query | string | No | Optional /proc/<pid>/stat field 22 (starttime in clock ticks since boot) to avoid PID reuse bugs. If it mismatches, command executes immediately |
defer_timeout_ms | query | integer | No | Max time to wait for defer_pid exit before failing (default: 60000) |
defer_poll_ms | query | integer | No | Poll interval while waiting for defer_pid exit (default: 50, minimum: 10) |
reset | query | boolean | No | Reset 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 |
cwd | query | string | No | Working directory for local bash sessions (ignored for SSH) |
cwd_auto_create | query | boolean | No | Auto-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) |
shell | query | string | No | Shell to use for local sessions: bash (case-insensitive), zsh, fish, sh, etc. (default: server startup command, only applies to new sessions or after reset) |
user | query | string | No | System user to spawn shell as (requires su permissions, only applies to new sessions or after reset) |
cmd | query | string | No | Base64-encoded command to execute automatically (works with both new and active shells, executes every time URL is visited) |
env | query | string | No | Environment variable in KEY=VALUE format (can be repeated for multiple variables, e.g., ?env=DEBUG=1&env=API_KEY=abc) |
skip_display_wait | query | boolean | No | Skip waiting for Hoody Display readiness before executing command. By default, if a DISPLAY is configured, the endpoint blocks until the display server on port 4000+display_num is ready (default: false) |
display_wait_timeout | query | integer | No | Timeout in seconds for display readiness wait (default: 10, capped at 10 seconds to prevent event-loop pin; values <= 0 or malformed also map to the 10-second cap). Ignored if skip_display_wait=true |
display | query | string | No | DISPLAY environment variable for X11 applications (auto-formats :display if number provided, e.g., ?display=1 becomes DISPLAY=:1) |
ssh_host | query | string | No | SSH server hostname or IP address (creates SSH session if provided with ssh_user) |
ssh_user | query | string | No | SSH username (required if ssh_host is provided) |
ssh_port | query | string | No | SSH port number (default: 22) |
ssh_password | query | string | No | SSH password for authentication (use with caution, prefer key-based auth) |
socks5_host | query | string | No | SOCKS5 proxy hostname for SSH connection |
socks5_port | query | string | No | SOCKS5 proxy port (default: 1080) |
socks5_user | query | string | No | SOCKS5 proxy username for authentication |
ssh_key | query | string | No | Base64-encoded SSH private key for key-based authentication (prefer over password-based auth) |
socks5_pass | query | string | No | SOCKS5 proxy password for authentication |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
command | string | Yes | The command to execute |
id | string | No | Custom command ID (numeric 1-65535, auto-generated if not provided) |
timeout | integer | No | Timeout in seconds (0 = no timeout, default: 0) |
wait | boolean | No | Whether to wait for completion (default: true; forced false when defer_pid is set) |
cwd | string | No | Working directory for command execution (for local bash only) |
env | object | No | Environment variables as key-value pairs |
{ "command": "ls -la", "timeout": 30, "wait": true}Response
Section titled “Response”Command started successfully.
{ "command_id": 12345, "status": "running", "started_at": "2024-01-15T10:30:00Z"}Bad request — missing or invalid parameters.
{ "statusCode": 400, "error": "VALIDATION_ERROR", "message": "The command field is required"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Invalid or missing parameters | Check parameter format and retry | Check parameter format and retry |
INVALID_TERMINAL_ID | Terminal ID must be numeric (1-65535) | Provide valid terminal_id | Provide valid terminal_id |
Forbidden — requested cwd cannot be created due to permissions.
{ "statusCode": 403, "error": "CWD_PERMISSION_DENIED", "message": "Requested working directory could not be created"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CWD_PERMISSION_DENIED | Requested working directory could not be created | Choose a writable path or disable cwd_auto_create | Choose a writable path or disable cwd_auto_create |
Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close.
{ "statusCode": 405, "error": "METHOD_NOT_ALLOWED", "message": "Request method is not POST"}Internal server error.
{ "statusCode": 500, "error": "EXECUTION_FAILED", "message": "Command execution failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
EXECUTION_FAILED | Command execution failed | Check terminal session status | Check terminal session status |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/execute?terminal_id=1" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "command": "ls -la", "timeout": 30, "wait": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.terminal.execution.execute( { command: 'ls -la', timeout: 30, wait: true }, { terminal_id: '1' });Retrieve results
Section titled “Retrieve results”GET /api/v1/terminal/result/{command_id}
Section titled “GET /api/v1/terminal/result/{command_id}”Retrieve the current or final results of a command execution. Can be called while a command is running or after completion.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
command_id | path | string | Yes | Command ID returned from /api/v1/terminal/execute (numeric 1-65535) |
Response
Section titled “Response”Command results (running or completed).
{ "command_id": 12345, "status": "completed", "stdout": "total 12\ndrwxr-xr-x 3 user user 4096 Jan 15 10:30 .\n", "stderr": "", "exit_code": 0, "started_at": "2024-01-15T10:30:00Z", "completed_at": "2024-01-15T10:30:01Z"}Command not found.
{ "statusCode": 404, "error": "COMMAND_NOT_FOUND", "message": "Verify command_id from execute response"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
COMMAND_NOT_FOUND | Command ID does not exist | Verify command_id from execute response | Verify command_id from execute response |
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/result/12345" \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.terminal.execution.getResult(command_id);Abort a command
Section titled “Abort a command”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.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
command_id | path | string | Yes | The command ID returned by the execute endpoint |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
force | boolean | No | Send SIGKILL to process group instead of SIGINT (default: false) |
{ "force": false}Response
Section titled “Response”Command aborted successfully.
{ "command_id": 12345, "status": "aborted", "signal": "SIGINT", "partial_output": "running for 30 seconds..."}curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/execute/12345/abort" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "force": false }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.terminal.terminal.abort(command_id, { force: false });Write terminal input
Section titled “Write terminal input”POST /api/v1/terminal/write
Section titled “POST /api/v1/terminal/write”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. Supports interactive prompts (y/n), sudo passwords, and any other stdin input.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | query | string | Yes | Terminal session ID to write to |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
input | string | Yes | The text to type into the terminal |
enter | boolean | No | Auto-append Enter (newline) after input. Default: true. Set to false for raw keystroke input |
{ "input": "y", "enter": true}Response
Section titled “Response”Input written successfully.
{ "terminal_id": 1, "bytes_written": 3}Bad request — missing parameters.
{ "statusCode": 400, "error": "MISSING_INPUT", "message": "input field is required in request body"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TERMINAL_ID | terminal_id query parameter is required | terminal_id query parameter is required | Contact support |
MISSING_INPUT | input field is required in request body | input field is required in request body | Contact support |
Terminal session not found.
{ "statusCode": 404, "error": "SESSION_NOT_FOUND", "message": "No terminal session with the given ID"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SESSION_NOT_FOUND | No terminal session with the given ID | No terminal session with the given ID | Contact support |
Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close.
{ "statusCode": 405, "error": "METHOD_NOT_ALLOWED", "message": "Request method is not POST"}No running process in session.
{ "statusCode": 409, "error": "NO_PROCESS", "message": "Terminal session has no running process to write to"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NO_PROCESS | Terminal session has no running process to write to | Terminal session has no running process to write to | Contact support |
Write failed.
{ "statusCode": 500, "error": "WRITE_FAILED", "message": "Failed to write input to PTY"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
WRITE_FAILED | Failed to write input to PTY | Failed to write input to PTY | Contact support |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com/api/v1/terminal/write?terminal_id=1" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "input": "y", "enter": true }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.terminal.terminal.write({ input: 'y', enter: true }, { terminal_id: '1' });