Skip to main content

Record & Export a Session

Capture what happens in a terminal, stop the capture, then export it as an asciinema cast, an HTML page, plain text, or raw JSON — all through TermFlow's local API.

TermFlow's recording feature turns a live terminal into a saved, replayable session. Every recording is driven by a small set of REST endpoints on the local API, so you can start and stop captures by hand with curl, or let a script (or an agent) do it for you. This tutorial walks the full loop: start → work → stop → inspect → export → play back.

Before you start

You need a running TermFlow instance with the API reachable at http://127.0.0.1:42031 (the default prod port), and at least one open terminal whose id you know. To list terminals or create one, see the Terminals API. On the default localhost binding every endpoint is unauthenticated — that is safe because the server is bound to 127.0.0.1. You only need an Authorization: Bearer <token> header when TermFlow is exposed on your LAN; see Local API & auth.

The recording loop

A recording is tied to a single terminal. You start it, the terminal keeps doing its work, you stop it, and TermFlow persists the result as a JSON recording you can then export or replay.

Step 1 — Pick a terminal to record

Every recording needs a terminal id. Grab one from the list of open terminals:

curl -s http://127.0.0.1:42031/api/terminals

Copy the id of the terminal you want to capture — we'll call it TERMINAL_ID below. (If you don't have one yet, POST /api/terminals creates one; the Terminals API page covers the body fields.)

Step 2 — Start recording

Send the terminal id to the start endpoint. The options object is optional; omit it to accept the defaults.

POST /api/recordings/start HTTP/1.1
Host: 127.0.0.1:42031
Content-Type: application/json

{
"terminal_id": "TERMINAL_ID"
}
curl -s -X POST http://127.0.0.1:42031/api/recordings/start \
-H "Content-Type: application/json" \
-d '{ "terminal_id": "TERMINAL_ID" }'

On success you get HTTP 201 and the new recording id — hold onto it, you'll need it for every later step:

{
"recordingId": "b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"terminalId": "TERMINAL_ID",
"status": "recording"
}

If the terminal id doesn't exist you get 404 { "error": "Terminal not found" }. If that terminal is already being recorded you get 409.

Recording options

Pass any of these inside an options object to change what is captured. All are optional; the defaults are shown.

FieldTypeDefaultWhat it does
include_outputbooleantrueCapture terminal output
include_inputbooleantrueCapture keystrokes sent to the terminal
include_resizebooleantrueCapture terminal resize events
compressionstring"gzip"Stored compressed flag ("none" or "gzip")
auto_stopbooleanfalseReserved for automatic stop behavior

The options object is parsed as a whole, so include every field when you send one — a partial object doesn't merge with the defaults:

curl -s -X POST http://127.0.0.1:42031/api/recordings/start \
-H "Content-Type: application/json" \
-d '{
"terminal_id": "TERMINAL_ID",
"options": {
"include_output": true,
"include_input": false,
"include_resize": true,
"compression": "gzip",
"auto_stop": false
}
}'

Step 3 — Do some work

With the recording active, use the terminal normally. It doesn't matter how the terminal is driven — anything the PTY emits is fair game for capture:

  • type directly in the terminal window,
  • push a command over the API with POST /api/terminals/TERMINAL_ID/input,
  • or let a connected agent run commands through the MCP server.

For a quick, self-contained example, send a command straight to the terminal:

curl -s -X POST http://127.0.0.1:42031/api/terminals/TERMINAL_ID/input \
-H "Content-Type: application/json" \
-d '{ "data": "echo hello from TermFlow\r" }'

You can check whether a terminal is currently being recorded at any time:

curl -s http://127.0.0.1:42031/api/recordings/status/TERMINAL_ID
# { "terminalId": "TERMINAL_ID", "isRecording": true, "status": "recording" }

Step 4 — Stop recording

Stop by recording id (not terminal id). This finalizes the recording, stamps its end time, and writes it to disk.

curl -s -X POST http://127.0.0.1:42031/api/recordings/stop/b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f
{
"recordingId": "b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"status": "stopped",
"size": 4096,
"eventCount": 128
}

size is the total captured payload in bytes and eventCount is how many events were recorded.

Step 5 — Inspect the recording

List every saved recording (newest first):

curl -s http://127.0.0.1:42031/api/recordings
{
"recordings": [
{
"id": "b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"terminalId": "TERMINAL_ID",
"startTime": "2026-07-04T10:15:00+00:00",
"endTime": "2026-07-04T10:17:30+00:00",
"size": 4096,
"eventCount": 128
}
]
}

For a single recording's summary — including its duration in milliseconds and metadata (title, shell type, initial size) — use the info endpoint:

curl -s http://127.0.0.1:42031/api/recordings/b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f/info
{
"id": "b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"terminalId": "TERMINAL_ID",
"startTime": "2026-07-04T10:15:00+00:00",
"endTime": "2026-07-04T10:17:30+00:00",
"eventCount": 128,
"size": 4096,
"compressed": true,
"duration": 150000,
"metadata": {
"title": null,
"shell_type": "bash",
"initial_size": { "cols": 80, "rows": 24 }
}
}

GET /api/recordings/{id} returns the complete recording object, including every captured event.

Step 6 — Export

Export takes the recording id and a format. The response is a file download (the server sets a Content-Disposition attachment header), so redirect it to a file with curl -o.

# Export as an asciinema cast
curl -s -X POST http://127.0.0.1:42031/api/recordings/b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f/export \
-H "Content-Type: application/json" \
-d '{ "format": "asciinema" }' \
-o session.cast
formatContent-TypeContains
jsonapplication/jsonThe complete recording: metadata and every event (output, input, resize)
texttext/plainA metadata header plus the concatenated output stream
htmltext/htmlA standalone dark-themed HTML page showing the output stream
asciinemaapplication/jsonAn asciinema v2 .cast file (one header line + one line per output event)
Output-only exports

Only the json format preserves every event type. The text, html, and asciinema exports include the terminal's output events — input keystrokes and resize events are not written into those three formats.

An unsupported format returns 400 { "error": "Unsupported format" }; an unknown recording id returns 404.

Play back an asciinema cast

The .cast file is standard asciinema v2, so any asciinema player understands it. With the asciinema CLI installed:

asciinema play session.cast

The header carries the terminal's initial width/height (from the recording's metadata.initial_size), the recording's start timestamp, and an env with SHELL/TERM, so playback matches the original geometry.

Play back inside TermFlow

TermFlow also ships a built-in playback viewer that replays a recording in a read-only terminal with a scrubber, speed control (0.25×–4×), and a loop toggle. Keyboard controls in the viewer:

KeyAction
SpacePlay / pause
EscStop
Shift+Step to previous
Shift+Step to next
HomeSeek to start
EndSeek to end

See Session recording for the in-app experience.

Clean up

Delete a recording you no longer need (returns 204 No Content):

curl -s -X DELETE http://127.0.0.1:42031/api/recordings/b0c1e2f3-4a5b-6c7d-8e9f-0a1b2c3d4e5f

Endpoint reference

Method & pathPurpose
POST /api/recordings/startStart recording a terminal (body: terminal_id, options?)
POST /api/recordings/stop/{id}Stop and persist a recording (id = recording id)
GET /api/recordingsList all saved recordings
GET /api/recordings/{id}Get a full recording, including events
GET /api/recordings/{id}/infoGet a recording's summary and metadata
POST /api/recordings/{id}/exportExport a recording (body: format)
DELETE /api/recordings/{id}Delete a recording
GET /api/recordings/status/{terminalId}Check whether a terminal is being recorded
GET /api/recordings/activeList the ids of all active recordings

An honest note: Session recording is part of TermFlow 0.1.0's early-access release. The Recordings API, all four export formats, and the playback viewer described on this page are implemented and working today; expect the capture and export pipeline to keep evolving as TermFlow moves past pre-release.

Next steps