This document defines the HTTP API surface for tcode-web based on the PoC in poc.md.
It is a product/API contract document, not an implementation plan. It focuses on:
- what endpoints the web server should expose
- how those endpoints map to session files and runtime actions
- which endpoints stream live events vs return current values
- how nested subagents should be addressed
The API should closely mirror the session files that already exist under a session directory.
Examples:
display.jsonlsession-meta.jsonconversation-state.jsonstatus.txtusage.txttoken_usage.txt- tool-call event files
- subagent event/state files
This keeps the web API aligned with the existing session data model.
Append-only event files should be exposed as streaming endpoints.
For the PoC, these are represented as file-like routes ending in .jsonl, but the response transport is Server-Sent Events (SSE).
The payload should remain close to the underlying file content instead of being transformed into a new normalized event model.
Non-append files should be returned with normal GET requests.
Examples:
session-meta.jsonconversation-state.jsonstatus.txtusage.txttoken_usage.txt
Some internal files use awkward names such as tool-call-call_<id>.jsonl.
The public API should expose those with cleaner stable routes such as:
/api/sessions/:sessionId/tool-calls/:toolCallId.jsonl/api/sessions/:sessionId/tool-calls/:toolCallId/status.txt
The backend maps those routes to the real session files.
Subagents can contain:
- tool calls
- subagents
The API should not use recursively nested URL paths like:
/api/sessions/:sessionId/subagents/:parentSubagentId/subagents/:childSubagentId/...
Instead, all subagents in a session should be addressed with a single flattened namespace:
/api/sessions/:sessionId/subagents/:subagentId/...
This applies even when a subagent is itself inside another subagent.
The parent/child hierarchy should be represented in the subagent data and events, not in the URL shape.
This keeps routes simple while still preserving the true nesting structure.
Most API endpoints and all protected SSE endpoints require authentication. The public exceptions are POST /api/auth/login, POST /api/auth/logout, and GET /api/auth/session.
The remote server uses per-user accounts with argon2id-hashed passwords stored in ~/.tcode/web-users.toml:
- users are created before server startup with
tcode add-web-user <username> - the client logs in by sending a username and password
- the server verifies the password against the stored argon2id hash
- the server returns an authenticated session cookie
- the browser uses that cookie for normal API requests and SSE connections
Security requirements:
- cookie-based authentication
- cookie must be
HttpOnly - cookie must be
Secureby default;tcode remote --allow-insecure-httpexplicitly omitsSecurefor trusted direct-HTTP testing - cookie must be
SameSite=Strict - do not put auth tokens in query parameters
- validate request origin for browser requests
- enforce origin checks strictly on all state-changing endpoints
- if the server is exposed remotely, it should be behind HTTPS or a trusted tunnel/proxy
- the remote server binds to localhost by default unless explicitly configured otherwise
The API has four groups:
- authentication endpoints
- session collection/lifecycle endpoints
- file-shaped read/stream endpoints
- action/proxy endpoints for runtime commands
Authenticate using a username and password.
Request body:
{
"username": "alice",
"password": "..."
}Response:
- success response
- sets authenticated session cookie
Clear the authenticated session cookie.
Return whether the current request is authenticated.
Example response:
{
"authenticated": true,
"secure_session_cookie": true,
"username": "alice"
}List existing sessions for the sidebar.
This returns lightweight summary data derived from session files such as:
- session id
- description/title when available
- creation time
- last active time
- current status
- session mode (
normalorweb-only)
Example response:
{
"sessions": [
{
"id": "53hthyc8",
"description": "Review this project",
"created_at": "2026-04-15T23:00:00Z",
"last_active_at": "2026-04-16T00:00:00Z",
"status": "Ready",
"mode": "normal"
}
]
}Create a new session.
The request may include an initial prompt.
The client must not be able to override the working directory. The working directory comes from where tcode remote was started.
Request body:
{
"initial_prompt": "Help me review this project"
}Response:
{
"id": "new-session-id"
}Notes:
titleis not required for the PoC- working directory override is not supported
- server/profile defaults are controlled by the backend startup environment
- session mode is always web-only for sessions created through the web API
Register a browser client lease for a session runtime. The frontend uses leases to keep an active runtime attached while a browser tab is open, and to reconnect or resume an inactive historical session when possible.
Request body fields:
client_label— optional human-readable client labelresume— optional boolean; when true, the server attempts to resume an inactive session fromconversation-state.json
The response includes whether a runtime is active, an optional client_id, lease/heartbeat timing, and runtime info.
Refresh a previously registered browser client lease and return current runtime info.
Release a browser client lease. The response is 204 No Content on success.
Move a session to the authenticated user's trash directory.
Active sessions are cancelled before moving. The session directory is moved to <trash_dir>/<session_id>. If a name collision occurs in the trash (for example because a CLI-created session used the same ID), a YYYYMMDDTHHMMSS timestamp suffix is appended.
The response is 204 No Content on success.
Status codes:
204 No Content— session moved to trash successfully401 Unauthorized— missing or invalid authentication404 Not Found— session does not exist or is not a web-only session500 Internal Server Error— filesystem error during the move
Return the current session metadata snapshot.
Return the current conversation state snapshot.
Return the current session status text.
Return the current usage stats text.
This is useful for displaying usage information in the web UI.
Return the current token usage text.
Stream the main conversation event file for the session.
Transport:
- SSE
Behavior:
- follows new appended events from
display.jsonl - payload stays close to the file-backed event data
- used by the main conversation display
These endpoints expose top-level tool calls belonging directly to the session.
Stream the append-only event file for a tool call.
Internal file mapping may point to a file like tool-call-call_<id>.jsonl, but the public route should use the cleaner tool-calls/:toolCallId.jsonl form.
Transport:
- SSE
Return the current tool-call status text.
Subagents should be addressed in a flattened session-scoped namespace.
This means that even if a subagent is nested inside another subagent, its API path is still:
/api/sessions/:sessionId/subagents/:subagentId/...
not a recursively nested route.
The true parent/child subagent structure should be represented in the subagent metadata and event data.
Return the current metadata snapshot for a subagent.
This should include enough information to identify its parent context if applicable.
Return the current conversation state snapshot for a subagent.
Return the current subagent status text.
Return the current token usage text for the subagent.
Stream the append-only event file for a subagent conversation.
Transport:
- SSE
Behavior:
- used by the subagent detail page
- payload remains close to the file-backed event stream
Subagents can have their own tool calls.
Those tool calls should be addressed under the owning subagent:
Stream the append-only event file for a subagent tool call.
Transport:
- SSE
Return the current status text for a subagent tool call.
These endpoints proxy or trigger runtime actions rather than exposing files directly.
Send a new user message to the root session.
Example request:
{
"text": "Please summarize this change"
}Send a new user message to a subagent conversation.
Request body shape is the same as the root message endpoint.
Finish the current user request for the root session.
Finish the current user request for a subagent conversation. The browser UI exposes this as the subagent "Done" action when the subagent has completed its reply.
Cancel the current root conversation or active run.
Cancel the current subagent conversation or active run.
Cancel a top-level tool call.
Subagent tool-call cancellation uses the parallel subagent-scoped route:
Cancel a tool call owned by a subagent.
The browser UI uses these endpoints to display pending permission requests and resolve them. The add/revoke endpoints are also available for clients or future UI controls that manage permission grants proactively.
Return the current permission state, including any pending permission requests relevant to the UI.
Approve or deny a pending permission request.
Example request:
{
"request_id": "...",
"decision": "approve"
}Add a permission rule.
Revoke a permission rule.
Routes ending in .jsonl represent append-only event files.
For the PoC they should:
- use SSE transport
- stream newly appended events
- stay close to the underlying file event format
- support live UI updates
Examples:
/api/sessions/:sessionId/display.jsonl/api/sessions/:sessionId/tool-calls/:toolCallId.jsonl/api/sessions/:sessionId/subagents/:subagentId/display.jsonl/api/sessions/:sessionId/subagents/:subagentId/tool-calls/:toolCallId.jsonl
Routes ending in .json return the current snapshot value for that resource.
Examples:
session-meta.jsonconversation-state.json
Routes ending in .txt return the current text value for that resource.
Examples:
status.txtusage.txttoken_usage.txt
Subagents may contain both tool calls and further subagents.
To keep the API stable and simple:
- all subagents are addressed directly under
/api/sessions/:sessionId/subagents/:subagentId/... - nested subagents are not exposed via recursive paths
- subagent ancestry is represented in data, not in URL nesting
Examples:
Good:
/api/sessions/abc/subagents/sub1/display.jsonl/api/sessions/abc/subagents/sub2/display.jsonl/api/sessions/abc/subagents/sub2/tool-calls/call9.jsonl
Not recommended:
/api/sessions/abc/subagents/sub1/subagents/sub2/display.jsonl
This flattened model should be called out explicitly in the API design because subagents can nest recursively.
Do not include the following for now:
- log endpoints for
debug.log,stdout.log, orstderr.log - file diff preview endpoints under
tool-file-preview/ - tree-view-specific APIs
POST /api/auth/loginPOST /api/auth/logoutGET /api/auth/session
GET /api/sessionsPOST /api/sessionsPOST /api/sessions/:sessionId/leasesPOST /api/sessions/:sessionId/leases/:clientId/heartbeatDELETE /api/sessions/:sessionId/leases/:clientIdDELETE /api/sessions/:sessionId
GET /api/sessions/:sessionId/session-meta.jsonGET /api/sessions/:sessionId/conversation-state.jsonGET /api/sessions/:sessionId/status.txtGET /api/sessions/:sessionId/usage.txtGET /api/sessions/:sessionId/token_usage.txtGET /api/sessions/:sessionId/display.jsonl
GET /api/sessions/:sessionId/tool-calls/:toolCallId.jsonlGET /api/sessions/:sessionId/tool-calls/:toolCallId/status.txtPOST /api/sessions/:sessionId/tool-calls/:toolCallId/cancel
GET /api/sessions/:sessionId/subagents/:subagentId/session-meta.jsonGET /api/sessions/:sessionId/subagents/:subagentId/conversation-state.jsonGET /api/sessions/:sessionId/subagents/:subagentId/status.txtGET /api/sessions/:sessionId/subagents/:subagentId/token_usage.txtGET /api/sessions/:sessionId/subagents/:subagentId/display.jsonl
GET /api/sessions/:sessionId/subagents/:subagentId/tool-calls/:toolCallId.jsonlGET /api/sessions/:sessionId/subagents/:subagentId/tool-calls/:toolCallId/status.txtPOST /api/sessions/:sessionId/subagents/:subagentId/tool-calls/:toolCallId/cancel
POST /api/sessions/:sessionId/messagesPOST /api/sessions/:sessionId/subagents/:subagentId/messagesPOST /api/sessions/:sessionId/finishPOST /api/sessions/:sessionId/subagents/:subagentId/finishPOST /api/sessions/:sessionId/cancelPOST /api/sessions/:sessionId/subagents/:subagentId/cancelGET /api/sessions/:sessionId/permissionsPOST /api/sessions/:sessionId/permissions/resolvePOST /api/sessions/:sessionId/permissionsDELETE /api/sessions/:sessionId/permissions/:permissionId
Suggested status code usage:
401 Unauthorizedfor missing or invalid authentication403 Forbiddenfor rejected origin or denied access404 Not Foundwhen the session, subagent, tool call, or file-shaped resource does not exist409 Conflictwhen an action is invalid for the current runtime state500 Internal Server Errorfor unexpected backend failures