Skip to main content

REST / WebSocket Alternative

Prefer to build your own tooling instead of wiring up an MCP client? TermFlow exposes the same terminal-driving power through a plain REST + WebSocket API on localhost — no MCP required.

info
The API lives on 127.0.0.1:42031

Every REST endpoint is under the base URL http://127.0.0.1:42031/api, and the live output stream is at ws://127.0.0.1:42031/ws. In the default localhost configuration these are bound to 127.0.0.1 and are unauthenticated — safe because nothing off your machine can reach them. (Ports are configurable in Settings → Connections.)

Why a REST API when there is MCP?

Because the MCP server is itself a client of this API. TermFlow runs one local API server (Axum, in Rust) on port 42031; the MCP sidecar on port 42032 is a thin bridge that translates MCP tool calls into calls against that same API. Anything an MCP agent can do, your own code can do directly.

So the choice is not "REST vs. the real thing" — both talk to the same engine. MCP is the zero-glue path for agents that already speak MCP; REST + WebSocket is the path when you are writing the orchestrator yourself.

What it gives you

The public surface covers the full terminal lifecycle. These are the endpoints you will reach for most when building tooling:

ActionMethod + path
List active terminalsGET /api/terminals
Create a terminalPOST /api/terminals
Inspect one terminalGET /api/terminals/:id
Close a terminalDELETE /api/terminals/:id
Read scrollback outputGET /api/terminals/:id/output
Grab a rendered snapshotGET /api/terminals/:id/snapshot
Send raw keystrokes/inputPOST /api/terminals/:id/input
Run a command or agent promptPOST /api/terminals/:id/execute
Fan one prompt out to manyPOST /api/terminals/batch/execute
Stream live outputws://127.0.0.1:42031/ws

There is more beyond this — profiles, session recordings, search, system/process info, and layout. See the API Reference for the complete, parameter-by-parameter listing.

An honest note: This is the lower-level path. You own the glue: HTTP client, retries, output parsing, and reconnection logic for the WebSocket. If your agent already speaks MCP, you will write far less code by using MCP instead.

A quick taste

Create a terminal, then run something in it. Note that REST create defaults to 80×24 (the MCP create_terminal tool defaults to 120×40), so pass cols/rows explicitly if you want a wider view:

# Create a terminal (localhost, no auth needed)
curl -X POST http://127.0.0.1:42031/api/terminals \
-H 'Content-Type: application/json' \
-d '{"name":"build","cols":120,"rows":40}'
# -> { "id": "term_abc123", ... }

Drive it. The execute endpoint submits a prompt and, via cliType (default copilot), adapts how the submission is sent for different agent CLIs:

curl -X POST http://127.0.0.1:42031/api/terminals/term_abc123/execute \
-H 'Content-Type: application/json' \
-d '{"prompt":"npm test","cliType":"default"}'

Read the result back out of the scrollback (offset 0 returns the last N lines):

curl 'http://127.0.0.1:42031/api/terminals/term_abc123/output?lines=50'

Stream output live over the WebSocket instead of polling. On connect you receive a welcome frame, then a stream of output.data events:

const ws = new WebSocket("ws://127.0.0.1:42031/ws");

ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
// First frame: { "id": "welcome", "success": true, "data": { "version": "0.1.0", "mode": "tauri" } }
if (msg.type === "event" && msg.event?.type === "output.data") {
process.stdout.write(msg.event.data.content);
}
};

// Send keystrokes back to a terminal:
ws.send(JSON.stringify({
type: "command",
payload: { action: "terminal:input", terminalId: "term_abc123", data: "ls\n" }
}));

See the WebSocket reference for the full frame catalog (heartbeat, subscribe, and the command envelope).

Authentication

In the default localhost mode, no token is required — the server trusts 127.0.0.1. Auth only switches on when you turn on "Expose on local network" in Settings → Connections. Once exposed:

  • REST requests must carry Authorization: Bearer <token>.
  • WebSocket connections pass the token as a query param — ws://<host>:42031/ws?token=<token> — because browsers cannot set custom headers on a WS handshake.

The token to use is the auth token shown (and rotatable) in Settings → Connections — the same authToken from your config file. /health and /api/health stay open regardless. Full details live in Local API and Auth.1

A quirk worth knowing

For backward compatibility, POST /api/terminals/:id/input, /size, and /resize return HTTP 200 with an { "error": ... } body when the target terminal does not exist — rather than a 404. Always check the response body, not just the status code.

REST vs. MCP — which should I use?

MCPREST + WebSocket
Best forAn agent that already speaks MCPBuilding your own orchestrator, dashboard, or integration
Glue codeNone — the agent calls the toolsYou write the HTTP/WS client
Language/runtimeWhatever your MCP client supportsAnything that can make an HTTP request
Live outputPoll (get_terminal_output)Push — subscribe to the WS output.data stream
Port4203242031
SetupPaste a config snippetPoint your client at the base URL

Reach for MCP when your agent is one of the first-class clients (Claude Code, Codex CLI, Gemini CLI) or any other MCP-capable tool — you get terminal control with zero integration code. See Connect an agent — Overview.

Reach for REST + WebSocket when you are writing the driver yourself: a custom orchestrator, a monitoring dashboard, a CI helper, or anything in a language/runtime where standing up an HTTP client is simpler than an MCP one — and especially when you want a live push stream of terminal output rather than polling.

Next steps

Footnotes

  1. There is a POST /api/auth/token endpoint, but the token it returns is not the credential the auth gate checks — do not use it to authenticate API calls. Always use the authToken from Settings → Connections.