API — Browser runtime
The endpoint family Tease Browser and the desktop app talk to after a device is paired — accounts, workspace state, proxy leases, activity, session lanes, and the panel-side sync feed. All under one prefix, one auth envelope.
Every endpoint below is mounted under /api/browser-runtime/v1, composed
in build_browser_runtime_router() (app/products/platform/api/ browser_runtime.py:133-200) and wired into the app via compose_browser_native
(app/main.py:126-130). This is the desktop app's own API — it is not called
from the panel's web SPA.
Every call carries a proof
Every endpoint (except /challenges itself, see below) takes a JSON body
shaped { "proof": InstallationProofBody, "body": <endpoint body> } — never a
bare payload. proof binds the call to one paired device installation via a
device-held signature; the endpoint-specific body is the actual request.
This is checked once per call by proof_context(...), so it is described here
instead of repeated per endpoint below.
A failed proof, a stale challenge, or a conflicting device state all come
back through the same raise_runtime_error() path — expect a small,
consistent set of error codes (invalid_grant, conflict codes, governor
503s) across this whole family rather than ad hoc per-endpoint errors.
Pairing and profile
| Endpoint | Purpose |
|---|---|
POST /challenges | Issues a signed challenge for the next proof (the one call that does not itself carry a proof — it takes {action, body_sha256} under a bearer install grant). |
POST /profiles/authorize | Authorizes a browser profile for interactive use on a platform. |
POST /profiles/proxy-lease | Leases the profile's sealed egress proxy — always re-authorizes the profile first. |
POST /workspace | Reads the installation's whole workspace state (accounts, platform readiness); optionally includes the activity-generation floor. |
Waiting and permits
POST /control/wait is a bounded long-poll: given after_revision and
wait_ms, it blocks until the account's connection state changes or the
timeout elapses, returning a revision snapshot either way — this is how the
desktop app avoids tight-polling connection status.
Two separate permit families gate concurrent access to a platform account's request budget — same claim → reservation → spread shape, different callers:
connect-permits/{acquire,cancel,outcome}— permits taken during the connect flow itself (see API — Connecting an account).permits/{acquire,cancel,outcome}— permits for ongoing runtime use after the account is already connected.
Accounts
POST /accounts— the installation's own list of connected platform accounts.POST /accounts/avatar— avatar image data for accounts in that list.
Activity (open / heartbeat / close)
POST /activity/{open,heartbeat,close} — the three-beat lifecycle of a live
account-viewing session: open it, heartbeat while the tab stays visible, close
it when done. This is what marks an account "actively watched" versus merely
connected — see Troubleshooting for
why a connected-but-idle account is not the same as a healthy one.
Session lane (/session-lane/*)
Nested under /api/browser-runtime/v1/session-lane. One account can only be
driven from one installation at a time; these endpoints negotiate the
handoff instead of silently kicking the other side:
POST /request-release, /status, /accept-release, /decline-release,
/force-release.
Session envelope and engine session import
POST /profiles/session-envelope hands the installation a sealed session
envelope for a profile. A second endpoint, POST /profiles/engine-session-import (in browser_runtime_profiles.py), imports a
session the engine captured directly — the profile-lifecycle counterpart to
/api/connect/import's extension-captured path.
Sync feed (/sync/*)
POST /sync/commands/accept, /sync/segments/{claim,renew,release,permit, result,outcome}, /sync/raw/reconcile — the panel-side view of the sync
engine's segment protocol. This is a different, parallel surface from the
Fansly engine's own sync endpoints on API — Fansly
sync: same segment vocabulary, same
claim/renew/release shape, but this family is mounted here under
/api/browser-runtime/v1/sync/* for the desktop app, not under the engine's
/api/platform-engine/v1/fansly/sync/*. Do not confuse the two when reading a
log line — check the full path.
Attached reconnect
/profiles/attached-reconnect/{begin,begin-exclusive-reauth,discover,prepare, finalize-intent,status,cancel} and /profiles/continue are also mounted
inside this same router (conditionally, only when the attached-reconnect
service is configured). Documented separately on API — Attached
reconnect because it is a
distinct recovery flow, not part of first-time connect.
What's next
For the login/pairing protocol that issues the device installation these calls authenticate against, see API — Native login.
API — Connecting an account
Endpoints that move a platform account from "extension has a session" to "Platform has a bound, syncing account" — token minting, saved proxies, session import, and the browser-profile v2 provisioning chain.
API — Native login
How the desktop app pairs a device and gets a token — an OAuth-authorization-code-and-PKCE-shaped flow with a device signature bolted on, plus its own in-app consent screen and lost-device recovery.