Skip to content

API Reference

TermBeam exposes a REST API and WebSocket interface.

All API endpoints (except /login, /api/auth, /api/version, and /api/config) require authentication via cookie or Bearer token.

Bearer auth failure (401):

{ "error": "unauthorized" }

Returned when the Authorization: Bearer token does not match the server password, or when an API request has no valid authentication. Bearer auth is also rate-limited to 5 attempts per minute per IP (returns 429).

Authenticate and receive a session token.

Request:

{ "password": "your-password" }

Response (200):

{ "ok": true }

Sets an httpOnly cookie pty_token.

Response (401):

{ "error": "wrong password" }

Response (429):

{ "error": "Too many attempts. Try again later." }

List all active sessions.

Response:

[
{
"id": "a1b2c3d4",
"name": "my-project",
"cwd": "/home/user/project",
"shell": "/bin/zsh",
"pid": 12345,
"clients": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"color": "#4a9eff",
"lastActivity": 1719849600000,
"git": {
"branch": "main",
"repoName": "owner/repo",
"provider": "GitHub",
"status": {
"clean": false,
"modified": 1,
"staged": 0,
"untracked": 2,
"ahead": 3,
"behind": 0,
"summary": "1 modified, 2 untracked, 3↑"
}
}
}
]
FieldTypeDescription
colorstringHex color assigned to the session
lastActivitynumberUnix timestamp (ms) of the last PTY output
cwdstringLive working directory of the shell process (updates when the user cds)
gitobject/nullGit repository info for the session’s cwd, or null if not in a git repo
git.branchstringCurrent branch name (or short SHA in detached HEAD)
git.repoNamestring/nullRemote repository name (e.g. owner/repo)
git.providerstring/nullHosting provider: GitHub, GitLab, Bitbucket, Azure DevOps, or host
git.statusobjectWorking tree status with clean, modified, staged, untracked, ahead, behind, and summary fields

Create a new session.

Request:

{
"name": "My Session",
"shell": "/bin/bash",
"args": ["-l"],
"cwd": "/home/user",
"initialCommand": "htop",
"color": "#4ade80",
"cols": 120,
"rows": 30
}

All fields are optional. If initialCommand is provided, it will be sent to the shell after startup (capped at 8192 characters — longer values are rejected with 400 initialCommand too long). If color is omitted, a color is assigned automatically from a built-in palette. The optional cols and rows fields set the initial terminal size (defaults to 120×30 if omitted).

The shell field is validated against the list of detected shells (see GET /api/shells). The cwd field must be an absolute path to an existing directory.

Response (201):

{
"id": "e5f6g7h8",
"url": "/terminal?id=e5f6g7h8"
}

Response (400):

{ "error": "Invalid shell" }
{ "error": "cwd must be an absolute path" }
{ "error": "cwd is not a directory" }
{ "error": "cwd does not exist" }

Update session properties.

Request:

{
"color": "#f87171",
"name": "renamed-session"
}

All fields are optional.

Response (200):

{ "ok": true }

Response (404):

{ "error": "not found" }

Scan a session’s scrollback buffer for the last localhost or 127.0.0.1 URL and return the port number.

Response (200) — port found:

{ "detected": true, "port": 3000 }

Response (200) — no port found:

{ "detected": false }

Response (404):

{ "error": "not found" }

Kill and remove a session.

Response (204): No content.

Response (404):

{ "error": "not found" }

Browse files and directories within a session’s working directory.

Query parameters:

ParameterTypeDescription
dirstringSubdirectory path relative to session CWD. Defaults to ..

Response (200):

{
"base": "/home/user/project/src",
"rootDir": "/home/user/project",
"entries": [
{
"name": "components",
"type": "directory",
"size": 0,
"modified": "2024-01-15T10:30:00.000Z"
},
{ "name": "index.ts", "type": "file", "size": 1234, "modified": "2024-01-15T10:30:00.000Z" }
]
}
FieldTypeDescription
basestringAbsolute path to the listed directory
rootDirstringAbsolute path to the session’s working directory
entriesarrayFiles and directories in the listed path

Each entry contains name (string), type ("file" or "directory"), size (number, bytes), and modified (string or null — ISO 8601 timestamp, or null if stat fails).

Entries are sorted directories-first, then files alphabetically. Hidden files (starting with .) and symbolic links are excluded.

Response (400):

{ "error": "Invalid dir parameter" }

Response (401):

{ "error": "unauthorized" }

Response (404):

{ "error": "Session not found" }

Response (500):

{ "error": "Failed to read directory" }

Download a file from within a session’s working directory.

Query parameters:

ParameterTypeDescription
filestringFile path relative to session CWD (required).

Response: Binary file content with Content-Disposition: attachment header.

Response (400):

{ "error": "Missing file parameter" }
{ "error": "Not a regular file" }

Response (401):

{ "error": "unauthorized" }

Response (403):

{ "error": "Symlink target is outside session directory" }

Response (404):

{ "error": "Session not found" }
{ "error": "File not found" }

Response (413):

{ "error": "File too large (max 100 MB)" }

Get the text content of a file. Used for in-browser file preview (e.g., markdown rendering).

Query parameters:

ParameterTypeDescription
filestringFile path relative to session CWD (required).

Response (200):

{
"content": "# Hello World\n\nThis is a markdown file.",
"name": "README.md",
"size": 42
}
FieldTypeDescription
contentstringUTF-8 text content of the file
namestringFilename (basename)
sizenumberFile size in bytes

Response (400):

{ "error": "Missing file parameter" }
{ "error": "Not a regular file" }

Response (401):

{ "error": "unauthorized" }

Response (403):

{ "error": "Symlink target is outside session directory" }

Response (404):

{ "error": "Session not found" }
{ "error": "File not found" }

Response (413):

{ "error": "File too large (max 2 MB)" }

Serve a file inline from a session’s working directory. Unlike the /download endpoint, this does not set a Content-Disposition: attachment header, making it suitable for in-browser rendering (e.g., images).

Query parameters:

ParameterTypeDescription
filestringFile path relative to session CWD (required).

Response: File content served inline with the appropriate Content-Type header.

Response (400):

{ "error": "Missing file parameter" }
{ "error": "Not a regular file" }

Response (401):

{ "error": "unauthorized" }

Response (403):

{ "error": "Symlink target is outside session directory" }

Response (404):

{ "error": "Session not found" }
{ "error": "File not found" }

Response (413):

{ "error": "File too large (max 20 MB)" }

List available shells on the host system.

Requires authentication (cookie or Bearer token).

Response:

{
"shells": [
{ "name": "bash", "path": "/bin/bash", "cmd": "/bin/bash" },
{ "name": "zsh", "path": "/bin/zsh", "cmd": "/bin/zsh" }
],
"default": "/bin/zsh",
"cwd": "/home/user"
}
FieldTypeDescription
namestringDisplay name of the shell
pathstringFull path to the shell executable
cmdstringOriginal command name (on Windows this differs from the full path)
defaultstringPath to the server’s default shell
cwdstringServer’s default working directory
---
#### `GET /api/share-token`
Generate a fresh share token for sharing access. The token is single-use and expires after 5 minutes. Requires authentication.
**Response (200):**
```json
{ "url": "https://your-tunnel-url/?ott=<token>" }

The returned URL auto-logs in whoever opens it. The token is consumed on first use and expires after 5 minutes. When accessed through a tunnel, the URL uses the public tunnel address.

Response (404):

{ "error": "auth disabled" }

Returned when the server was started with --no-password.


Get the server version. Recomputed per request so dev source checkouts surface new commits and dirty state without a restart. Production installs short-circuit to the cached package.json value, so the cost is negligible.

Response:

{ "version": "1.0.0" }

Get public server configuration. No authentication required.

Response:

{ "passwordRequired": true }
FieldTypeDescription
passwordRequiredbooleanWhether the server requires password to access

Check if a newer version of TermBeam is available on npm. Results are cached for 24 hours. Requires authentication (session cookie or Authorization: Bearer <password>) when authentication is enabled. If the server is started with --no-password, this endpoint is accessible without authentication.

Query parameters:

ParameterTypeDescription
forcebooleanBypass cache and fetch fresh data

Response:

{
"current": "1.10.2",
"latest": "1.11.0",
"updateAvailable": true,
"method": "npm",
"command": "npm install -g termbeam@latest",
"canAutoUpdate": true,
"restartStrategy": "exit"
}
FieldTypeDescription
methodstringHow TermBeam was installed: npm, npx, yarn, pnpm, docker, or source
commandstringSuggested update command for this installation method
canAutoUpdatebooleanWhether the in-app update mechanism can auto-update this installation
restartStrategystringHow the server will restart after update: pm2 (PM2 managed restart), exit (clean exit), or none

When no update is available or the check fails, updateAvailable is false and latest may be null.

Trigger an in-app update. Only works when canAutoUpdate is true (npm/yarn/pnpm global installs). Rate-limited to 1 request per 5 minutes.

Response (success):

{
"status": "updating",
"method": "npm"
}

Response (not supported):

{
"error": "Auto-update not available for this installation method",
"method": "source",
"command": "git pull && npm install && npm run build:frontend",
"canAutoUpdate": false
}

Response (already in progress):

Returns HTTP 409 with the current update state.

Response (rate-limited):

Returns HTTP 429 when rate limit (1 request per 5 minutes) is exceeded:

{
"error": "Update already attempted recently. Try again in a few minutes."
}

After triggering, progress is broadcast via WebSocket update-progress messages. The server will either restart (PM2) or exit cleanly (non-PM2) once the update is verified.

Poll the current update status. Useful as a fallback when WebSocket is not connected.

Response:

{
"status": "idle",
"phase": null,
"progress": null,
"error": null,
"fromVersion": null,
"toVersion": null,
"startedAt": null,
"restartStrategy": null
}
StatusDescription
idleNo update in progress
checking-permissionsVerifying write access to global npm prefix
installingRunning package manager install command
verifyingChecking installed version after update
restartingUpdate verified, server restarting
completeUpdate finished successfully
failedUpdate failed (see error field)

List subdirectories for the folder browser.

Response:

{
"base": "/home/user",
"dirs": ["/home/user/projects", "/home/user/documents"],
"truncated": false
}
FieldTypeDescription
basestringAbsolute path that was listed
dirsarrayAbsolute paths of subdirectories
truncatedbooleantrue when results were cut off at the 500-entry limit

Upload an image file. The request body is the raw image data with the appropriate Content-Type header (e.g., image/png, image/jpeg).

Request headers:

  • Content-Type: Must be an image/* type

Response (201):

{ "id": "uuid", "url": "/uploads/uuid", "path": "/tmp/termbeam-uuid.png" }

Response (400):

{ "error": "Invalid content type" }
{ "error": "No image data" }
{ "error": "File content does not match declared image type" }

Returned when the file’s magic bytes don’t match the declared Content-Type header.

Response (413):

{ "error": "File too large" }

Maximum file size is 10 MB.

Upload a file to a session’s working directory. The request body is the raw file content. The filename is provided via the X-Filename header and sanitized server-side (path traversal sequences are stripped). Duplicate filenames are auto-renamed (e.g., file (1).txt).

Request headers:

  • Content-Type: The file’s MIME type (e.g., application/octet-stream)
  • X-Filename: Original filename (required)
  • X-Target-Dir: Override destination directory (optional, defaults to session cwd)

Response (201):

{ "name": "script.sh", "path": "/home/user/project/script.sh", "size": 1024 }

Response (400):

{ "error": "Missing X-Filename header" }
{ "error": "Invalid filename" }
{ "error": "Empty file" }

Response (404):

{ "error": "Session not found" }

Response (413):

{ "error": "File too large (max 10 MB)" }

Serve a previously uploaded file by its opaque ID. Requires authentication.

Response: The file content with appropriate content type.

Response (404):

{ "error": "not found" }

Returns parsed git status for a session’s working directory.

Response (200):

{
"branch": "main",
"ahead": 3,
"behind": 0,
"staged": [{ "path": "src/index.js", "status": "M", "oldPath": null }],
"modified": [{ "path": "README.md", "status": "M", "oldPath": null }],
"untracked": ["new-file.txt"],
"isGitRepo": true
}
FieldTypeDescription
branchstringCurrent branch name
aheadnumberCommits ahead of upstream
behindnumberCommits behind upstream
stagedarrayStaged files, each with path, status, and oldPath (for renames)
modifiedarrayModified files, each with path, status, and oldPath (for renames)
untrackedarrayUntracked file paths
isGitRepobooleanWhether the session’s cwd is inside a git repository

Returns file diff for a specific file.

Query parameters:

ParameterTypeDescription
filestringFile path relative to repo root (required)
stagedbooleanShow staged changes instead of working tree
untrackedbooleanTreat file as untracked (diff against empty)
contextnumberNumber of context lines around changes

Response (200):

{
"file": "src/index.js",
"hunks": [
{
"header": "@@ -10,6 +10,7 @@",
"oldStart": 10,
"oldLines": 6,
"newStart": 10,
"newLines": 7,
"lines": [
{
"type": "context",
"content": "const express = require('express');",
"oldLine": 10,
"newLine": 10
},
{
"type": "add",
"content": "const cors = require('cors');",
"oldLine": null,
"newLine": 11
}
]
}
],
"additions": 1,
"deletions": 0,
"isBinary": false
}
FieldTypeDescription
filestringFile path
hunksarrayDiff hunks with header, line ranges, and lines
additionsnumberTotal number of added lines
deletionsnumberTotal number of deleted lines
isBinarybooleanWhether the file is binary (no line-level diff)

Each line in a hunk contains type ("add", "remove", or "context"), content, oldLine, and newLine.


Returns per-line blame information for a file.

Query parameters:

ParameterTypeDescription
filestringFile path relative to repo root (required)

Response (200):

{
"file": "src/index.js",
"lines": [
{
"line": 1,
"content": "const express = require('express');",
"commit": "a1b2c3d",
"author": "Jane Doe",
"date": "2025-01-15T10:30:00.000Z",
"summary": "Initial commit"
}
]
}
FieldTypeDescription
filestringFile path
linesarrayPer-line blame entries

Each line entry contains line (number), content (string), commit (short hash), author, date (ISO 8601), and summary (commit message first line).


Returns commit log for the repository.

Query parameters:

ParameterTypeDescription
limitnumberMax commits to return (default 20, max 100)
filestringFilter commits to those touching this file path

Response (200):

{
"commits": [
{
"hash": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"shortHash": "a1b2c3d",
"author": "Jane Doe",
"email": "jane@example.com",
"date": "2025-01-15T10:30:00.000Z",
"subject": "feat: add git integration",
"body": ""
}
]
}
FieldTypeDescription
commitsarrayList of commits

Each commit contains hash, shortHash, author, email, date (ISO 8601), subject, and body.


Returns the VAPID public key needed to create a push subscription on the client.

Response (200):

{ "publicKey": "BNq..." }
FieldTypeDescription
publicKeystringBase64url-encoded VAPID public key

Register a Web Push subscription with the server.

Request:

{
"subscription": {
"endpoint": "https://fcm.googleapis.com/fcm/send/...",
"keys": {
"p256dh": "BNq...",
"auth": "abc..."
}
}
}

Response (200):

{ "ok": true }

Remove a previously registered push subscription.

Request:

{ "endpoint": "https://fcm.googleapis.com/fcm/send/..." }

Response (200):

{ "ok": true }

Returns the current tunnel connection state, provider, and token lifetime.

Response (200):

{
"state": "connected",
"provider": "microsoft",
"tokenLifetimeSeconds": 3200
}
FieldTypeDescription
statestringTunnel state: connected, disconnected, expiring, auth-expired, or unknown
providerstring/nullAuth provider used for the tunnel: microsoft, github, or null if no tunnel is active
tokenLifetimeSecondsnumber/nullSeconds remaining on the current auth token, or null if unavailable

Reverse-proxies requests to a service running on 127.0.0.1:<port>. All HTTP methods are supported. The path after the port is forwarded as-is, along with query strings and request headers.

Requires authentication (cookie or Bearer token).

Response: The upstream response is streamed back with its original status code and headers.

Response (400):

{ "error": "Invalid port: must be an integer between 1 and 65535" }

Response (502):

{ "error": "Bad gateway: upstream server is not responding" }

Returned when the upstream service is unreachable or the connection is refused.

Response (504):

{ "error": "Gateway timeout: upstream server did not respond in time" }

Returned when the upstream service does not respond within 10 seconds.


GET /api/agents

Returns AI coding agents detected in the server’s PATH.

Response:

{
"agents": [
{
"id": "copilot",
"name": "GitHub Copilot",
"cmd": "copilot",
"args": [],
"icon": "copilot",
"version": "1.2.3"
}
]
}

Results are cached for 60 seconds. If both copilot and gh copilot are available, only the standalone copilot is returned.

GET /api/agent-sessions?limit=100&agent=copilot&search=auth

Returns past sessions from Copilot (SQLite) and Claude Code (JSONL) for resume.

ParameterDefaultDescription
limit100Max sessions to return (1–500)
agentallFilter by agent: copilot or claude
searchSearch in summary, CWD, repo, branch

Response:

{
"sessions": [
{
"id": "35b1faca-...",
"agent": "copilot",
"agentName": "GitHub Copilot",
"agentIcon": "copilot",
"summary": "Review Copilot Session Portal",
"cwd": "/Users/dev/projects/termbeam",
"repo": "user/repo",
"branch": "main",
"updatedAt": "2026-04-04T10:11:01.609Z",
"turnCount": 23
}
]
}

Sessions with zero user turns are excluded. Copilot session reading requires better-sqlite3 (listed in optionalDependencies — installs automatically when native build tools are available, gracefully skipped otherwise).

GET /api/agent-sessions/:agent/:id/resume-command

Returns the CLI command to resume a specific agent session.

Response:

{ "command": "copilot --resume=35b1faca-..." }

Connect to ws://host:port/ws.

If the server has a password set and the WebSocket connection wasn’t authenticated via cookie, send an auth message first. When the server is started with --no-password, authentication is skipped automatically.

{ "type": "auth", "password": "your-password" }

or with an existing token:

{ "type": "auth", "token": "session-token" }

Response:

{ "type": "auth_ok" }

Auth Failure:

{ "type": "error", "message": "Unauthorized" }

The connection is closed after sending this message. Sending any non-auth message before authenticating also results in this error and connection closure.

{ "type": "attach", "sessionId": "a1b2c3d4" }

After a successful attach, the server sends a single replay message containing the session’s sanitized scrollback buffer (up to ~1,000,000 characters; trimmed back to ~500,000 when it grows beyond that), followed by the attached confirmation. Clients should treat replay as an authoritative state snapshot and reset their terminal before applying it — this prevents duplicated content on reconnect when the terminal UI is preserved across socket lifecycles.

{ "type": "input", "data": "ls -la\r" }
{ "type": "resize", "cols": 120, "rows": 30 }

The server validates resize dimensions: cols must be between 1–500 and rows between 1–200. Values outside these bounds are ignored.

{ "type": "output", "data": "..." }

Sent on attach. Contains sanitized scrollback (and an alt-screen re-entry sequence when the session is currently in alt-screen). Clients should drop pending writes, reset their terminal, and write this payload — do not append it on top of preserved terminal content.

{ "type": "replay", "data": "..." }
{ "type": "attached", "sessionId": "a1b2c3d4" }
{ "type": "exit", "code": 0 }

Sent when a command completes (child process exits). Broadcast in real time to connected clients and replayed on attach for events that occurred while disconnected.

{
"type": "notification",
"notificationType": "command-complete",
"sessionName": "my-project",
"timestamp": 1719849600000
}
FieldTypeDescription
notificationTypestringNotification kind (command-complete)
sessionNamestringName of the session where the event fired
timestampnumberUnix timestamp (ms) when the event occurred
{ "type": "error", "message": "Session not found" }

Sent during an in-app update (triggered via POST /api/update). Allows the frontend to show real-time progress without polling.

{
"type": "update-progress",
"status": "installing",
"phase": "Installing update...",
"progress": "added 42 packages...",
"error": null,
"fromVersion": "1.14.0",
"toVersion": null,
"restartStrategy": "exit"
}

The status field follows the same values as GET /api/update/status. When status reaches restarting, the WebSocket connection will close shortly after (close code 1012 for non-PM2 installs).

Broadcast when the tunnel connection state changes. Allows the frontend to show tunnel health and prompt for re-authentication when tokens expire.

{
"type": "tunnel-status",
"state": "expiring",
"expiresIn": 1800,
"provider": "microsoft"
}
FieldTypeDescription
statestringTunnel state: connected, disconnected, expiring, auth-expired, reconnecting, or failed
expiresInnumberSeconds until the auth token expires (present when state is expiring or connected)
providerstringAuth provider: microsoft or github (present when a tunnel is active)

  • Architecture — system design, module responsibilities, and data flow
  • Security — threat model, safe usage, and security features