Skip to content

HTTP & WebSocket API

A practical scripting reference for the bridge's HTTP endpoints and WebSocket protocols.

Authentication for scripts

Every endpoint requires the bridge password except a small public allowlist (/health, /login, /sw.js, and a few login-page assets). For scripts and curl, send it as a Bearer token — the password lives in ~/.config/painapple-code/config.yaml:

# Liveness check — no auth required
curl http://localhost:8765/health

# Authenticated request
TOKEN=$(awk '/^password:/ {print $2}' ~/.config/painapple-code/config.yaml)
curl -H "Authorization: Bearer $TOKEN" http://localhost:8765/api/welcome/projects

Browsers use the bridge_auth cookie or a one-time ?tkn=<password> query parameter instead; the Authorization header is the HTTP-only path meant for scripts. See First run & login.

Endpoint groups

Not exhaustive — a map of where things live, with representative routes.

Group Prefix Examples
Chat ws://…/chat Main Claude WebSocket (see protocol)
Terminal ws://…/ws/terminal PTY WebSocket; also GET /api/terminals, DELETE /api/terminal/{id}, GET /api/active-sessions
Sessions /api/sessions, /api/session/{id} CRUD, POST /api/session/{id}/fork, PUT /api/session/{id}/permission-mode, GET /api/session/{id}/threads
Logs /api/sessions/{id}/logs …/logs/messages, …/logs/raw, …/logs/tools, GET /api/sessions/{id}/changes
Files /api/files, /api/file Directory listing, GET /api/file?path=…, POST /api/file/write, GET /api/projects
Git /api/git Status, diff, log, show
Bridge /api/bridge GET/POST /api/bridge/tabs, GET /api/bridge/presets, GET/PUT /api/bridge/config, GET /api/info
Project config /api/project GET/PUT /api/project/config, POST /api/project/rename
Stash /api/session/{id}/stash GET/POST/DELETE stash items; GET /api/favorites
Welcome /api/welcome GET /api/welcome/sessions, POST /api/welcome/search, GET /api/welcome/projects
Shadow git /api/shadow Branches, log, undo, restore, search, file timelines
Shadow DB /api/shadow-db, /api/turns Turn queries, tags, stats, raw SQL
Prompts /api/prompts Prompt search, recent, frequent, stats
Costs /api/costs Summary, per-session, per-tool, trends
Tasks /api/tasks GET /api/tasks, GET /api/tasks/{task_id} (background task output)
Commands /api/commands Slash-command catalog
Agents /api/agents Agent templates
Plugins /api/plugins Plugin discovery
Skills /api/skills Skill catalog
Exec /api/exec POST /api/exec?command=…&cwd=… — shell execution (powers bang commands)
Upload /api/upload-image, /api/upload-file Image and file uploads
Viewer /view, /api/file-raw Raw file serving and the file-viewer page
Browser /api/browser …/render, …/proxy — local HTML rendering + external URL proxy

WebSocket chat protocol

Connect to ws://…/chat with query parameters:

Param Meaning
session Server-side session ID to join or resume an existing session
cwd Working directory (used when creating a new session)

Sessions are bound to session IDs, not connections — reconnecting to a running session resumes its output stream.

Client → server

Type Payload
user_message {"type": "user_message", "content": "prompt", "images": [{"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "…"}}]}images optional
ping Keepalive; server replies pong
stop Kill the running turn
clear_session Archive and reset the session
tool_answer Answer to an AskUserQuestion tool prompt
set_permission_mode {"type": "set_permission_mode", "mode": "acceptEdits"} — applied on the next turn

Server → client

Type Meaning
connected Handshake — includes session_id, cwd, is_reconnect, agent_running
agent_message Wraps provider-neutral Claude-shaped JSON (system / assistant / user / result) in data
raw_output Unparsed subprocess output line
stderr Subprocess stderr / server error text
user_message_stored Acknowledges your prompt was persisted
stopped Turn killed after a stop request
session_cleared Session reset after clear_session
permission_mode_changed Echo of a set_permission_mode request
compact_progress Progress while a compaction runs
session_ended Claude process exited (reason included)
error Anything else that went wrong
pong Reply to ping

Terminal WebSocket

Connect to ws://…/ws/terminal?session=<id>&cwd=<path>:

  • Client → server: raw keystrokes as text, plus one JSON control message: {"type": "resize", "rows": 40, "cols": 120}.
  • Server → client: raw ANSI terminal output.

Each session gets its own persistent PTY that survives disconnects; cwd is only used when the session has no stored working directory.

Shadow DB SQL

POST /api/shadow-db/sql runs ad-hoc read-only SQL against the shadow DuckDB of turns, costs, and tags.

  • Body: raw SQL (Content-Type: text/plain) or {"sql": "…"} JSON.
  • Format: default JSON {columns, rows, count}; ?format=tsv returns tab-separated text with a header row.
  • Read-only: a validator rejects mutation keywords (INSERT, UPDATE, DROP, ATTACH, …) and file-access functions.
shadow-query() { curl -sS -X POST "${BRIDGE_URL:-http://localhost:8765}/api/shadow-db/sql?format=tsv" \
  -H "Authorization: Bearer $(awk '/^password:/ {print $2}' ~/.config/painapple-code/config.yaml)" \
  -H "Content-Type: text/plain" --data-binary "$1"; }

shadow-query 'SELECT started_at, user_prompt[:80], cost, model FROM turns ORDER BY started_at DESC LIMIT 10'

Drop ?format=tsv for JSON output that pipes cleanly into jq.