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.
127.0.0.1:42031Every 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:
| Action | Method + path |
|---|---|
| List active terminals | GET /api/terminals |
| Create a terminal | POST /api/terminals |
| Inspect one terminal | GET /api/terminals/:id |
| Close a terminal | DELETE /api/terminals/:id |
| Read scrollback output | GET /api/terminals/:id/output |
| Grab a rendered snapshot | GET /api/terminals/:id/snapshot |
| Send raw keystrokes/input | POST /api/terminals/:id/input |
| Run a command or agent prompt | POST /api/terminals/:id/execute |
| Fan one prompt out to many | POST /api/terminals/batch/execute |
| Stream live output | ws://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
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?
| MCP | REST + WebSocket | |
|---|---|---|
| Best for | An agent that already speaks MCP | Building your own orchestrator, dashboard, or integration |
| Glue code | None — the agent calls the tools | You write the HTTP/WS client |
| Language/runtime | Whatever your MCP client supports | Anything that can make an HTTP request |
| Live output | Poll (get_terminal_output) | Push — subscribe to the WS output.data stream |
| Port | 42032 | 42031 |
| Setup | Paste a config snippet | Point 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
- Tutorial: Build an orchestrator on REST — a worked, end-to-end example.
- API Reference — Overview and Auth — the complete endpoint catalog and the auth model in full.
Footnotes
-
There is a
POST /api/auth/tokenendpoint, but the token it returns is not the credential the auth gate checks — do not use it to authenticate API calls. Always use theauthTokenfrom Settings → Connections. ↩