Skip to main content

The Integration Builder

You want to program TermFlow, not just use it: spawn terminals, fan a command out to a fleet of panes, tail live output, and wire it all into your own scripts, dashboards, or a custom MCP client. This page is the map from "I have an idea" to the exact endpoints, auth rules, and tools that make it real.

TermFlow exposes two local interfaces over a single localhost port pair:

  • a REST + WebSocket API for building your own tooling, and
  • a built-in MCP server that any MCP client can drive.

Both talk to the same terminal engine, so you can mix them freely — create a terminal over REST, drive it from an MCP client, and watch its output on the WebSocket, all at once.

Who this page is for

Developers writing orchestrators, CI glue, monitoring dashboards, bots, or a bespoke MCP client against TermFlow 0.1.0 (Early access · pre-release, Apache-2.0). If you just want to point an existing agent at TermFlow, start at Connect an agent instead.

The 30-second mental model

  • The app runs a local API server (Axum, Rust) on port 42031 — REST plus a WebSocket stream.
  • A separate MCP sidecar process listens on port 42032 and bridges MCP clients to that same API.
  • Everything binds to 127.0.0.1 by default. Nothing is on the network until you deliberately turn it on.

Both ports are user-overridable in Settings → Connections (range 1024–65535). This page uses the production defaults throughout; if you run a dev build the equivalents are 42051 (API) and 42052 (MCP).

Choose your integration surface

You want to…UseStart at
Script terminals from any language over HTTPREST APIAPI reference: overview
Stream live terminal output as it happensWebSocketAPI reference: WebSocket
Fan one command out to many terminalsBatch executeAPI reference: execute & batch
Drive TermFlow from an MCP-speaking agent or your own MCP clientMCP toolsAPI reference: MCP tools
Which one should I reach for?

If your consumer is an LLM agent, the MCP tools give it a small, well-typed toolset for free. If your consumer is code you control — a script, a service, a dashboard — the REST + WebSocket API is more direct and needs no MCP runtime. They are two front doors to the same engine, so it is fine to use both.

Auth: understand this before you write a line of code

This is the single most important thing to get right, and it is easy to over-engineer.

Auth is only enforced when you expose TermFlow on the LAN

In the default localhost-only mode, every endpoint is unauthenticated. That is safe because the server is bound to 127.0.0.1 — only processes on your own machine can reach it. Auth becomes mandatory the moment you turn on "Expose on local network" in Settings → Connections (which rebinds to 0.0.0.0), and only then.

The credential model in full:

  • The token is a bearer tokennetwork.authToken, a 64-hex string generated on first run and stored in TermFlow's config file. It is shown (masked, with reveal / copy / Rotate) in Settings → Connections.
  • When exposed on the LAN, send it on REST calls as Authorization: Bearer <token>.
  • WebSocket auth uses a ?token= query parameter, not a header — browsers cannot set headers on a WebSocket handshake.
  • GET /health and GET /api/health are always exempt, exposed or not, so you can health-check without a credential.
  • You can rotate the token in Settings → Connections without restarting the server; existing UI connections survive.
# Localhost (default): the header is optional and ignored by the gate
GET http://127.0.0.1:42031/api/terminals

# Exposed on LAN: the header is required
GET http://192.168.1.50:42031/api/terminals
Authorization: Bearer <your-64-hex-authToken>

An honest note: There is a POST /api/auth/token endpoint that mints a JWT. Do not use it to authenticate your integration — the auth gate does not check that JWT. The only credential that matters is the config authToken shown in Settings → Connections. Treat /api/auth/token as legacy monitor-compatibility scaffolding and ignore it.

For the full picture, see Local API & auth and the API reference overview.

REST API tour

Base URL: http://127.0.0.1:42031/api. Every route below is gated only when TermFlow is exposed on the LAN (see above).

GroupEndpointsNotes
HealthGET /health, GET /api/healthReturns {status, app, instanceId}. Always open.
TerminalsGET/POST /api/terminals; GET/DELETE /api/terminals/:id; GET/POST /api/terminals/:id/size; POST /api/terminals/:id/resize; POST /api/terminals/:id/input; GET /api/terminals/:id/output; GET /api/terminals/:id/snapshotCreate body: cols?=80, rows?=24, profileId?|profile?, name?, cwd?, tabId?, paneId?, direction?.
Execute / batchPOST /api/terminals/:id/execute (alias /prompt); POST /api/terminals/batch/execute; POST /api/terminals/batch/inputFan-out to many terminals in one call.
ProfilesGET/POST /api/profiles; GET/PUT/DELETE /api/profiles/:idA profile is {id, name, path, args[], env{}, cwd?, icon?, is_default, is_custom}.
System / processesGET /api/system/info; GET /api/processes; GET /api/processes/:id/metrics; GET /api/system/tmux-status/api/processes includes per-terminal foreground process and agent detection.
RecordingsPOST /api/recordings/start; POST /api/recordings/stop/:id; GET /api/recordings; GET/DELETE /api/recordings/:id; GET /api/recordings/:id/info; POST /api/recordings/:id/export; GET /api/recordings/status/:terminalId; GET /api/recordings/activeExport format: json | text | html | asciinema.
SearchPOST /api/search; GET /api/search/suggestionsQuery terminal history.
LayoutGET/POST /api/layoutRead or restore the tab/pane layout.
A REST default that will bite you

POST /api/terminals defaults to 80×24 if you omit cols/rows. The MCP create_terminal tool defaults to 120×40. If your integration relies on a specific width, set cols/rows explicitly rather than trusting the default.

Create, drive, read — the minimal loop

# 1. Create a terminal (returns an id)
curl -s -X POST http://127.0.0.1:42031/api/terminals \
-H 'Content-Type: application/json' \
-d '{"name":"builder","cols":120,"rows":40}'

# 2. Send a command as if typed (prompt = the text to run)
curl -s -X POST http://127.0.0.1:42031/api/terminals/<id>/execute \
-H 'Content-Type: application/json' \
-d '{"prompt":"echo hello from TermFlow"}'

# 3. Read the output back
curl -s "http://127.0.0.1:42031/api/terminals/<id>/output"

The execute body takes prompt (required), cliType? (default copilot), submissionSignal?, and customPattern?. See Execute & batch for how cliType shapes submission for different agent CLIs.

Fan a command out to many terminals

The batch endpoints are the heart of any orchestrator. POST /api/terminals/batch/execute takes terminalIds[] plus a prompt and returns a per-terminal result set:

{
"results": [
{ "terminalId": "abc", "success": true },
{ "terminalId": "def", "success": false }
],
"summary": { "total": 2, "succeeded": 1, "failed": 1 }
}

Use POST /api/terminals/batch/input when you want to send raw keystrokes (data) rather than a submitted prompt. There is a full worked example in Build an orchestrator on REST.

One quirk worth hardcoding a check for

An honest note: POST /api/terminals/:id/input, POST /api/terminals/:id/size, and POST /api/terminals/:id/resize return HTTP 200 with {"error": ...} in the body when the terminal id does not exist — a deliberate back-compat behavior. A 200 status alone does not mean success. Always inspect the response body for an error field, not just the status code.

The following routes exist in the codebase but are stubs — do not build against them: POST /api/profiles/:id/default, POST /api/terminals/:id/reset, GET /api/system/metrics (returns zeros), and GET /api/search/history (returns []).

WebSocket: live output stream

For anything real-time — tailing output, reacting to what an agent prints — poll nothing; open the WebSocket.

Endpoint:  ws://127.0.0.1:42031/ws   (alias: /api/ws)
Auth: append ?token=<authToken> only when exposed on the LAN

On connect the server sends a welcome frame, then a stream of events:

// welcome frame
{ "id": "welcome", "success": true, "data": { "version": "0.1.0", "mode": "tauri" } }

// server -> client: a chunk of terminal output
{ "type": "event",
"event": { "type": "output.data", "terminalId": "abc", "data": { "content": "..." } },
"timestamp": 0 }

Client → server frames:

SendPurpose
heartbeatKeep the connection alive.
subscribeSubscribe to a terminal's events.
command with payload.action = "terminal:input" (terminalId, data)Type into a terminal over the socket.

A typical dashboard opens one socket, subscribes to the terminals it cares about, renders output.data frames as they arrive, and sends terminal:input commands for interactivity. Full frame reference: WebSocket API.

MCP tools: the agent-facing surface

If your integration is an MCP client (or you are pointing an agent at TermFlow), you get 7 tools on the MCP sidecar at http://127.0.0.1:42032/mcp. The server identifies itself as auto-terminal-mcp, speaks streamable HTTP (POST / GET SSE / DELETE on /mcp), and exposes its own /health.

ToolKey paramsNotes
list_terminals(none)Lists active terminals.
create_terminalname?, profile?, cols?=120, rows?=40, cwd?, tabId?, paneId?, direction?direction: horizontal splits right, vertical splits bottom.
execute_commandterminalId (string or string[], required), command (required), cliType?, useBracketedPaste?An array of ids fans out as a batch. Async — returns immediately.
get_terminal_outputterminalId (required), lines?=50, offset?=0Clean text in raw, plus totalLines, offset. Offset 0 = last N lines.
get_terminal_detailterminalId (required)Includes tabId.
get_my_terminal(none)Resolves the caller's own terminal via the identity header.
close_terminalterminalId (required)"me" self-terminates.

Two details that make MCP integrations pleasant:

  • The "me" shorthand. Every tool that takes a terminalId accepts the literal "me". An agent running inside a TermFlow terminal is injected a $TERMFLOW_TERMINAL_ID; passing the X-Termflow-Terminal-Id header lets it read and write "its own" terminal without ever discovering an id. get_my_terminal resolves the same thing.
  • create_terminal defaults to 120×40 — wider than the REST default (80×24). Set cols/rows if you need a specific size.

The sidecar reads these environment variables (useful if you spawn it yourself): MCP_PORT (42032), MCP_HOST (127.0.0.1), AUTO_TERMINAL_API_URL (http://localhost:42031), AUTO_TERMINAL_TOKEN (preferred) or AUTO_TERMINAL_API_TOKEN (fallback), MCP_PARENT_PID, and AUTO_TERMINAL_INSTANCE_ID.

Bringing your own MCP client

TermFlow generates paste-ready config for three first-class clients — Claude Code, Codex CLI, and Gemini CLI — via Settings → Connections → "Connect an AI agent". Each uses the server key auto-terminal and the URL http://127.0.0.1:42032/mcp. See Claude Code, Codex CLI, and Gemini CLI.

Any other MCP client that speaks streamable HTTP (for example Cursor or Goose) works too — point it at http://127.0.0.1:42032/mcp as a generic HTTP MCP server. aider needs an MCP wrapper to bridge to it. These are honestly "also works because they speak MCP," not first-class integrations, so you configure them by hand. Details: Other MCP clients.

TermFlow brings no AI of its own

TermFlow ships no models and no keys. Your integration supplies its own agent and its own credentials; TermFlow is the terminal those agents drive. Hosting agents inside TermFlow (ACP) is on the roadmap, not a shipping feature today.

A practical build order

+--------------------------------------------------------------+
| 1. Health-check GET /api/health (no auth) |
| 2. Decide auth localhost = none; LAN = Bearer token |
| 3. Create POST /api/terminals (set cols/rows) |
| 4. Drive POST .../execute or batch/execute |
| 5. Observe WS /ws -> subscribe -> output.data |
| 6. Clean up DELETE /api/terminals/:id |
+--------------------------------------------------------------+

Start against localhost with no token, confirm the loop end to end, and only add the Authorization header and ?token= once you flip "Expose on local network" on. Because health is always open, GET /api/health is a reliable readiness probe for your startup sequence — and its instanceId lets you detect when you are talking to a different TermFlow instance than you expect.

Next steps