TEASEDocs
ProductsAccount Platform

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

EndpointPurpose
POST /challengesIssues 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/authorizeAuthorizes a browser profile for interactive use on a platform.
POST /profiles/proxy-leaseLeases the profile's sealed egress proxy — always re-authorizes the profile first.
POST /workspaceReads 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.

On this page