Developer guide

Remote Control API

The desktop app is one client of Caroline's own HTTP API. A second listener exposes that exact same API to another device — your phone, a server, a script — authenticated by your own SquirrelWisdom account. Not a curated subset: the full surface, status and diagnostics included.

How it's wired

Caroline's Python backend serves one FastAPI app on two listeners at once:

ListenerBindWho it's forAuth
PORT (default 48765)127.0.0.1 The WPF desktop shell, on the same machineNone — unchanged, this is the existing local trust boundary
REMOTE_PORT (default 48768)0.0.0.0 Anything reaching this machine from elsewhereSquirrelWisdom session, see below

A request's own connection tells the middleware which listener accepted it — not a header the caller could claim — so the two surfaces can't be confused with each other. Requests on the local port behave exactly as they always have; nothing about this feature changes how the desktop app itself talks to its own backend.

Authentication

Every request on the remote port must carry a bearer token:

Authorization: Bearer <your SquirrelWisdom v2 session>

The backend resolves that token against SquirrelWisdom's own user:getSessionUser call and compares the email it gets back to whichever SquirrelWisdom account Caroline herself is currently logged into. They have to match exactly. A session that's expired, invalid, or simply belongs to someone else's account is rejected — there's no shared backdoor, no separate API key to leak, and no way to control a Caroline instance you aren't already the owner of.

SituationResponse
No Authorization header401
Caroline isn't logged into SquirrelWisdom at all503 — nothing to compare the caller against
Token doesn't resolve, or resolves to a different account403
Token resolves to the same accountRequest proceeds normally

Getting a token today: there's no "copy my remote-access token" button in Settings yet — that's the natural next step, not built. Until then this is for anyone comfortable minting a SquirrelWisdom v2 session the same way Caroline's own backend does (user:verify against SquirrelWisdom's API with a scoped key) — see backend-py/app/plugins/sw_api.py in the open-source repo for exactly how Caroline does it herself.

Network reach is on you. Binding 0.0.0.0 makes the port listen on every interface, but nothing in Caroline arranges for it to be reachable from outside your own network — that's a router port-forward, a tunnel, or a VPN, whichever you'd set up for any other self-hosted service. There's also no rate limiting or replay protection on this port yet; treat the port itself as something you expose deliberately, not by default.

Plain HTTP, not HTTPS. The remote listener doesn't terminate TLS itself — your bearer token and everything else travel in cleartext unless you put it behind something that does (a tunnel, a VPN, or a reverse proxy with its own certificate). Don't send a real session token over the open internet without one.

Endpoint reference

Everything the desktop app itself calls is available. A few of the ones worth knowing by name:

EndpointWhat it does
GET /api/statusConnection state, per-tab status, workspace disk usage
POST /api/messageSend text (+ attachments) into a tab — fire-and-forget, 202
POST /api/controlThe same op-dispatch channel the chat window's own WebSocket uses — mode switches, login flows, and more, by op name
GET/POST /api/modeRead or switch a tab's answer source (Claude / SquirrelWisdom / OpenAI)
GET /api/companion/*Phone-companion data: SMS threads, contacts, paired devices, logs
GET /api/local-fileRead a file on Caroline's own machine — exactly as powerful as it sounds, gated the same way as everything else here

Checking status

curl -H "Authorization: Bearer $SW_SESSION" \
  http://your-host:48768/api/status

Sending a message

/api/message answers immediately with 202 and does the actual work in the background — Caroline's reply streams out over the WebSocket the desktop app uses, not back over this request. Poll /api/status (or read /api/companion/logs) to see what she said.

curl -X POST -H "Authorization: Bearer $SW_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"text": "What'"'"'s on my calendar today?"}' \
  http://your-host:48768/api/message

A diagnostic control call

curl -X POST -H "Authorization: Bearer $SW_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"type":"control_request","op":"ratatosk_channel_status","requestId":"1"}' \
  http://your-host:48768/api/control

Source

Every piece of this — the middleware, the session lookup, the full route list — is in the open-source backend: backend-py/app/main.py (the _require_remote_owner_session middleware) and backend-py/app/plugins/sw_api.py (get_session_user). Nothing here is undocumented beyond what's in this page and the source itself.

Browse the source