Skip to content
Hoody.com

The Terminal Automation API lets you drive TUI programs programmatically. Snapshot the rendered screen, search for regex matches, press keys, paste text, send mouse events, and block on conditions — all without driving an xterm.js frontend in a browser. Every operation is backed by a server-side libvterm parser that mirrors the session’s terminal state, so a snapshot returned by the API reflects exactly what a user would see in the terminal.

Returns a rendered snapshot of the terminal screen as seen by a user: the visible text grid (lines), cursor position, window title, fullscreen (alt-screen) state, reverse-video highlight spans, and a monotonic sequence counter. Optionally includes ANSI SGR colored lines. On the first call for a session, the parser is lazily initialized by replaying the session’s output buffer, so the snapshot reflects the full terminal history.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID (numeric 1-65535)
include_colorsquerybooleanNoInclude ANSI SGR colored_lines array alongside plain text lines. Default: false
include_highlightsquerybooleanNoInclude reverse-video highlight spans. Default: true
scroll_offsetqueryintegerNoLines into scrollback (0 = live viewport). Default: 0
{
"terminal_id": 12,
"lines": [
"$ npm install",
"audited 1247 packages in 3s",
"found 0 vulnerabilities",
"$ █"
],
"colored_lines": null,
"cursor": { "row": 3, "col": 3 },
"title": "bash — node-app",
"alt_screen": false,
"highlights": [
{ "row": 1, "start_col": 0, "end_col": 34 }
],
"sequence": 4821
}
Terminal window
curl -G "https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/snapshot" \
--data-urlencode "terminal_id=12" \
--data-urlencode "include_colors=true"

Searches the rendered terminal screen (or scrollback) for a PCRE2 regular expression pattern. Returns cell-coordinate hits with matched text. Supports case-insensitive matching, result limits, and scope selection (screen, scrollback, or all). Pattern length is capped at 1024 bytes. Match limits are enforced to prevent ReDoS.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID
patternquerystringYesPCRE2 regex pattern to search for (max 1024 bytes)
scopequerystringNoSearch scope: screen (default), scrollback, or all
limitqueryintegerNoMaximum number of hits to return (default 100, max 1000)
case_insensitivequerybooleanNoCase-insensitive matching. Default: false
scroll_offsetqueryintegerNoScrollback offset for screen scope (0 = live viewport). Default: 0
{
"terminal_id": 12,
"total": 3,
"truncated": false,
"deadline_exceeded": false,
"hits": [
{ "row": 4, "col": 0, "length": 11, "text": "Error: file" },
{ "row": 17, "col": 12, "length": 11, "text": "Error: file" },
{ "row": 22, "col": 4, "length": 11, "text": "Error: file" }
]
}

The deadline_exceeded flag is true when the scan hit the internal 500 ms wall-clock bound (ReDoS-shaped patterns); truncated is true when total >= limit.

Terminal window
curl -G "https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/find" \
--data-urlencode "terminal_id=12" \
--data-urlencode "pattern=Error:.*" \
--data-urlencode "scope=screen" \
--data-urlencode "limit=100"

GET /api/v1/terminal/{terminal_id}/automation

Section titled “GET /api/v1/terminal/{terminal_id}/automation”

Returns the VT parser state for a specific session: whether vterm is active, dimensions, update sequence counter, time since last screen change, alt-screen flag, title, scrollback length, and active waiter count. Useful for debugging automation workflows — for example, “why did my wait timeout? did the screen actually change?”.

NameInTypeRequiredDescription
terminal_idpathstringYesTerminal session ID
{
"terminal_id": 12,
"vterm_active": true,
"dimensions": { "rows": 24, "cols": 80 },
"sequence": 4821,
"time_since_change_ms": 142,
"alt_screen": false,
"title": "bash — node-app",
"scrollback_lines": 312,
"active_waiters": 1
}
Terminal window
curl "https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/12/automation"

Returns global metrics for the server-side VT parser: active vterm session count, memory used/cap in MB, total active wait-waiters across all sessions, and configured limits. Use to monitor resource usage, tune --vterm-memory-cap-mb, and detect leaks.

This endpoint takes no parameters.

{
"active_sessions": 47,
"memory_used_mb": 128.4,
"memory_cap_mb": 512,
"active_waiters": 12,
"max_sessions": 256,
"max_waiters_per_session": 16
}
Terminal window
curl "https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/automation/metrics"

These endpoints inject input into a terminal session: keystrokes, pasted text, and cell-based mouse events. All three share the same per-session atomicity contract — input is validated before any of it is sent, so a single invalid key (or out-of-range coordinate) rejects the entire request with no partial writes.

Returns the full list of key names accepted by /api/v1/terminal/press, including aliases and canonical forms. Useful for client-side validation and discoverability. Single printable characters (a-z, 0-9, punctuation) are also accepted but not listed individually.

This endpoint takes no parameters.

{
"keys": [
"enter", "tab", "escape", "backspace", "space",
"arrow_up", "arrow_down", "arrow_left", "arrow_right",
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12",
"ctrl+c", "ctrl+d", "ctrl+z", "ctrl+l",
"insert", "delete", "home", "end", "page_up", "page_down",
"esc", "cr", "lf", "bs", "del"
]
}
Terminal window
curl "https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/keys"

Sends one or more named key presses to a terminal session. Keys are encoded through libvterm’s keyboard API which respects the terminal’s current application-cursor mode (DECCKM) and keypad mode (DECKPAM), ensuring correct byte sequences for programs like vim, htop, and tmux. Supports letters, ctrl+letter, arrow keys, function keys, enter, tab, escape, backspace, and more. All keys are validated before any are sent — a single unknown key rejects the entire request with no partial writes.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID

Exactly one of key or keys must be supplied.

FieldTypeRequiredDescription
keystringNoSingle key name for one-shot press (e.g. enter). Mutually exclusive with keys
keysarrayNoArray of key names to press in sequence (e.g. ["ctrl+c", "arrow_up", "enter"]). Mutually exclusive with key. Maximum 256 entries per request
{
"terminal_id": 12,
"keys_pressed": 3,
"bytes_written": 6
}

For status 405, the body uses session_readonly for read-only (PID-attach) sessions and method_not_allowed (with an Allow: POST header) for wrong methods.

Terminal window
curl -X POST \
"https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/press?terminal_id=12" \
-H "Content-Type: application/json" \
-d '{ "keys": ["ctrl+c", "arrow_up", "enter"] }'

Pastes text into a terminal session with optional bracketed paste mode. When bracketed=true (default), the text is wrapped in bracketed paste escape sequences if the running program has enabled DECSET 2004 (e.g. vim, zsh). This prevents auto-indent mangling and other paste artifacts. When bracketed=false, the text is sent as raw keystrokes. UTF-8 text including emoji and CJK is fully supported.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID
FieldTypeRequiredDescription
textstringYesText to paste (UTF-8)
bracketedbooleanNoUse bracketed paste mode if the program supports it. Default: true
{
"terminal_id": 12,
"bytes_written": 41,
"bracketed_active": true,
"esc_neutralized": 0
}

bracketed_active reflects whether libvterm actually emitted the \e[200~...\e[201~ envelope (requires the running program to have enabled DECSET 2004). esc_neutralized is the count of input CSI-starter codepoints substituted with U+FFFD inside the envelope body — this covers both 7-bit ESC (U+001B) and 8-bit C1 CSI (U+009B), because libvterm can emit the envelope markers in either form depending on whether S8C1T is active. Neutralization prevents an embedded \e[201~ (or \u009b201~) in the paste from ending the frame early and letting the tail run as unwrapped keyboard input. When bracketed=false or the envelope is not in effect, esc_neutralized is always 0.

For status 405, the body uses session_readonly for read-only sessions and method_not_allowed (with an Allow: POST header) for wrong methods.

Terminal window
curl -X POST \
"https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/paste?terminal_id=12" \
-H "Content-Type: application/json" \
-d '{ "text": "git status\n", "bracketed": true }'

Sends deterministic mouse events to a terminal session using libvterm’s mouse API. Coordinates are zero-based terminal cells, not pixels. Mouse protocol output is emitted only when the target program has enabled terminal mouse reporting. Events are validated before any are sent, matching the all-or-nothing contract of /press.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID

The body must contain exactly one of event (single event) or events (array of 1-256 events).

FieldTypeRequiredDescription
eventobjectNoA single TerminalMouseEvent
eventsarrayNoArray of TerminalMouseEvent objects (minItems 1, maxItems 256)
FieldTypeRequiredDescription
typestringYesMouse event kind. One of move, down, up, click, scroll. click expands to down/up; scroll uses wheel buttons
rowintegerYesZero-based terminal row cell (≥ 0)
colintegerYesZero-based terminal column cell (≥ 0)
buttonintegerNoMouse button. Non-scroll events accept 1-3; scroll accepts 4-5
amountintegerNoScroll repeat count for scroll events (1-20)
directionstringNoOptional scroll direction (up or down); overrides the scroll button
modifiersarrayNoKeyboard modifiers applied to the mouse event. Each entry is one of shift, alt, meta, ctrl, control (maxItems 8)
{
"terminal_id": 12,
"events_processed": 2,
"bytes_written": 18
}

For status 405, the body uses session_readonly for read-only (PID-attach) sessions and method_not_allowed (with an Allow: POST header) for wrong methods.

Terminal window
# Single event
curl -X POST \
"https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/mouse?terminal_id=12" \
-H "Content-Type: application/json" \
-d '{
"event": {
"type": "click",
"row": 10,
"col": 20,
"button": 1,
"modifiers": ["ctrl"]
}
}'
# Batch of events
curl -X POST \
"https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/mouse?terminal_id=12" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "type": "down", "row": 10, "col": 20, "button": 1 },
{ "type": "up", "row": 10, "col": 20, "button": 1 }
]
}'

Blocks until a terminal condition is met, then returns an atomic snapshot of the screen at the moment of resolution. Supports three modes: stable (no screen updates for debounce_ms), regex (PCRE2 pattern matches on screen), or either (first condition wins). The response includes a full snapshot for the matched/stable/timeout/exited terminal statuses so clients avoid a TOCTOU race between wait and a follow-up /snapshot call; the vterm_reinit status is the lone exception — it fires when the VT parser was torn down mid-wait and no coherent snapshot can be captured (client should retry). Maximum 16 concurrent waiters per session.

NameInTypeRequiredDescription
terminal_idquerystringYesTerminal session ID
FieldTypeRequiredDescription
modestringNoWait mode: stable, regex, or either. Default: stable
debounce_msintegerNoStable mode debounce in milliseconds (10-60000). Default: 100
patternstringNoPCRE2 regex pattern (required for regex/either modes, max 1024 bytes)
timeout_msintegerNoHard deadline in milliseconds (10-300000). Default: 5000
search_scopestringNoWhere to search: screen, scrollback, or all. Default: screen
include_colorsbooleanNoInclude colored_lines in response snapshot. Default: false
include_highlightsbooleanNoInclude highlights in response snapshot. Default: true
{
"terminal_id": 12,
"status": "matched",
"match": { "row": 8, "col": 4, "length": 7, "text": "ready >" },
"snapshot": {
"lines": [
"Initializing...",
"Loading config...",
"Ready.",
"$ ready >█"
],
"cursor": { "row": 3, "col": 9 },
"title": "bash — node-app",
"alt_screen": false,
"sequence": 5012
}
}

The status field is one of:

  • matched — regex hit. Response includes match and snapshot.
  • stable — no damage for debounce_ms. Response includes snapshot.
  • timeout — hit timeout_ms. Response includes snapshot.
  • exited — underlying process died mid-wait. Response includes snapshot.
  • vterm_reinit — the VT parser was torn down and re-initialized mid-wait due to a memory-cap resize. Client should retry. Response includes neither match nor snapshot.
Terminal window
# Stable mode: wait for the screen to settle, then snapshot
curl -X POST \
"https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/wait?terminal_id=12" \
-H "Content-Type: application/json" \
-d '{
"mode": "stable",
"debounce_ms": 200,
"timeout_ms": 10000
}'
# Regex mode: wait until "ready >" appears anywhere on the screen
curl -X POST \
"https://myproject-mycontainer-terminal-1.us-east-1.containers.hoody.icu/api/v1/terminal/wait?terminal_id=12" \
-H "Content-Type: application/json" \
-d '{
"mode": "regex",
"pattern": "ready >",
"timeout_ms": 15000,
"search_scope": "screen"
}'