Execute & Batch
Send a prompt (or a raw command) into one terminal — or fan the same prompt out to many terminals at once — over the local REST API. These are the endpoints an orchestrator uses to "type" into a terminal on behalf of an agent.
Base URL: http://127.0.0.1:42031/api
In the default localhost-only mode every endpoint is unauthenticated — it is safe because the server is bound to 127.0.0.1. A bearer token is required only when you turn on Expose on local network in Settings → Connections. See Overview & auth for the full model.
What "execute" actually does
An execute call does two things to the target terminal's PTY, in order:
- Writes your
promptas a bracketed paste (wrapped inCSI 200~ … 201~), so embedded newlines are inserted as literal multi-line input rather than submitting early. - Writes a submit sequence — the keystrokes that make the target program act on what was just pasted.
The submit sequence is chosen from cliType (e.g. Copilot vs Claude vs a plain shell) unless you override it with submissionSignal or customPattern. This is why execute is aimed at driving interactive AI CLIs that need a specific key combination to submit a prompt — not just running shell commands.
An honest note: execute is fire-and-forget. The call writes the keystrokes and returns immediately — it does not wait for the command or agent to finish, and it does not return the terminal's output. To read what happened, poll
GET /api/terminals/:id/outputor subscribe to the WebSocket output stream.
Execute a prompt in one terminal
POST /api/terminals/:id/execute
POST /api/terminals/:id/prompt (alias — identical behavior)
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
prompt | string | Yes | — | Must be non-empty. Sent as a bracketed paste; may contain newlines. |
cliType | string | No | copilot | Selects the submit sequence. See the table below. |
submissionSignal | string | No | — | Exact keystroke sequence to send after the prompt. Highest priority — overrides cliType and customPattern. |
customPattern | object | No | — | Used only when cliType is custom. Shape: { "separator"?: string, "end_indicator": string }. |
cliType values
| Value | Intended target |
|---|---|
copilot | GitHub Copilot CLI (the default) |
claude | Claude Code / Claude CLI |
gemini | Gemini CLI |
chatgpt | ChatGPT-style CLI |
default | A plain shell command (submits with a normal Enter) |
custom | Use the keystrokes in customPattern |
An unrecognized cliType (that is not custom and has no built-in pattern) returns 400 Unknown CLI type: <value>.
Example — send a prompt to a Claude terminal
curl -X POST http://127.0.0.1:42031/api/terminals/term-abc123/execute \
-H "Content-Type: application/json" \
-d '{ "prompt": "Refactor utils.ts and run the tests", "cliType": "claude" }'
Success returns 200 with:
{
"success": true,
"prompt": "Refactor utils.ts and run the tests",
"cliType": "claude"
}
Errors (single execute)
| Status | Body | When |
|---|---|---|
400 | { "error": "Prompt must be a non-empty string" } | prompt missing or empty |
400 | { "error": "Unknown CLI type: <value>" } | Unrecognized cliType |
404 | { "error": "Terminal not found" } | No terminal with that id |
Unlike the raw /input, /size, and /resize endpoints — which return 200 with an { "error": ... } body for a missing terminal — single execute returns a real 404. Check the status code and the body.
Submission signals
submissionSignal lets you specify the exact keystrokes sent after the prompt, which is essential for interactive CLIs that submit on an unusual combination. Use standard JSON string escaping for control characters.
{ "prompt": "ls -la", "submissionSignal": "\r\n" }
| Signal | Meaning | Typical use |
|---|---|---|
\r | Carriage return | Standard macOS/Linux shell submit |
\r\n | CRLF | Standard Windows shell submit |
\r\r | Double return | AI CLIs that submit on an empty line |
\u001b\r | Escape + Enter | Claude CLI submission |
\r\n\r\n | Double CRLF | Robust submit for Node-based CLIs |
\u0003 | Ctrl+C | Send an interrupt |
\u0004 | Ctrl+D | Send EOF |
Precedence: submissionSignal wins over everything. If it is present, cliType and customPattern are ignored. When absent and cliType is custom, customPattern.end_indicator (optionally preceded by separator) is used; otherwise the built-in cliType sequence applies.
Fan out to many terminals
Send the same prompt to a list of terminals in one call. Ids are de-duplicated (first-seen order preserved), so one terminal is never written twice.
POST /api/terminals/batch/execute
Request body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
terminalIds | string[] | Yes | — | Non-empty list of terminal ids. |
prompt | string | Yes | — | Non-empty. |
cliType | string | No | copilot | Same values as single execute. |
submissionSignal | string | No | — | Same semantics as single execute. |
customPattern | object | No | — | Same shape as single execute. |
The submit pattern is validated once up front. An empty terminalIds, an empty prompt, an unknown cliType, or a missing customPattern (when cliType is custom) fails the whole batch with 400 — because the pattern is identical for every id, a malformed pattern is a bad request, not a per-terminal failure.
Example
curl -X POST http://127.0.0.1:42031/api/terminals/batch/execute \
-H "Content-Type: application/json" \
-d '{
"terminalIds": ["term-a", "term-b", "term-c"],
"prompt": "git pull && bun install",
"cliType": "default"
}'
Response
The batch always returns 200 with a per-terminal results array plus a summary. A bad id (terminal not found) becomes a per-terminal failure — it never blocks the others:
{
"results": [
{ "terminalId": "term-a", "success": true },
{ "terminalId": "term-b", "success": true },
{ "terminalId": "term-c", "success": false, "error": "Terminal not found" }
],
"summary": { "total": 3, "succeeded": 2, "failed": 1 }
}
Fan out raw input
Write a raw string to many terminals — no prompt wrapping, no submit sequence appended. This is the batch equivalent of the single-terminal /input endpoint: what you send is exactly what the PTYs receive.
POST /api/terminals/batch/input
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
terminalIds | string[] | Yes | Non-empty; de-duplicated in order. |
data | string | Yes | Raw bytes to write. Include your own \r/\n if you want submission. |
Example — interrupt two terminals at once (Ctrl+C)
curl -X POST http://127.0.0.1:42031/api/terminals/batch/input \
-H "Content-Type: application/json" \
-d '{ "terminalIds": ["term-a", "term-b"], "data": "\u0003" }'
Returns the same { results, summary } shape as batch execute, always with 200.
Fan-out flow
Agents connected over MCP get the same capability through the execute_command tool — passing an array of terminal ids fans the command out just like /batch/execute. See MCP tools.
Next steps
- Fan a command to many panes — a worked end-to-end example using these endpoints.
- Terminals — create terminals and read their output after you execute.