TEASEDocs
ProductsAccount Platform

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.

All endpoints on this page are mounted by make_connect_router() at /api/connect (app/main.py:134-135). They are called by the browser extension and by Tease Browser during account connection — not by the panel's own SPA screens.

POST /api/connect/token

Mints a short-lived, single-use connect token the app uses to open an account's live browser tab. Auth: panel session cookie, and the caller's role must have the browser tab granted (403 browser_not_granted otherwise).

Response: { "token": string, "ttl": number } (seconds).

This is a real permission boundary

The panel's usual session_scope fence covers /api/admin/*. This router has a different prefix, so /token checks browser access explicitly — otherwise a chatter's session could open a live OnlyFans tab it was never granted.

GET /api/connect/extension/start

Tokenless login for the browser extension: a top-level navigation so the panel's session cookie rides along. An already-logged-in creator is redirected straight back to the extension with a fresh connect token in the URL fragment (never sent to a server, never logged); a logged-out one is bounced through the panel's own login first. Query param: redirect_uri (validated — 400 bad redirect_uri if it doesn't match the extension's expected shape).

GET /api/connect/proxies

Lists the caller's saved proxies, for the extension's proxy picker before import. Auth: Authorization: Bearer <connect token> — a peek, it does not consume the token (only /import does). Returns 401 if the token is missing or invalid; a workspace owner (not a creator) gets {"items": []}.

Response: { "items": [{ id, name, host, port, protocol, has_password, last_status }] }. Never returns the proxy password.

POST /api/connect/proxies/{proxy_id}/lease

Short credential lease for Tease Browser: resolves one saved proxy to { server, protocol, username, password, expires_at }, where server is a bare host:port (no scheme — the shell builds the scheme from protocol itself; changing this shape breaks the shell's proxy setup). Same bearer-token auth as /proxies, same non-consuming peek. Errors: 404 (proxy not found for this owner), 503 (stored password can't be decrypted right now).

POST /api/connect/import

Receives a captured OnlyFans (or Fansly) session from the extension and hands it to the connection engine — the panel itself never touches OnlyFans here. Auth: Authorization: Bearer <connect token>, and this call consumes it (single-use). Rate-limited per owner and per IP (429 on either limit).

  • Body: JSON with platform, and for OnlyFans auth_id, cookies, xbc, user_agent; Fansly takes a different shape (local_storage-carried session, no xbc) and is routed internally to its own handler.
  • If saved_proxy_id is present, the account's pod is provisioned on that exit before import — an OF account never imports onto a floating exit. A page already bound to a different saved proxy gets 409 (changing exit is a separate, explicit action).
  • Errors: 401 (bad/expired/reused token), 422 (missing session material, unknown platform, or the engine rejected the session), 503 (engine unavailable), 404 (the chosen proxy wasn't found for this owner).
  • Success: { "ok": true, "account": "<external id>" }.

WS /api/connect/ws

Bridges the panel to the tunnelled connection engine for the live-view flow. Query param: token (the same single-use connect token, consumed on connect). Only one live socket per owner at a time — a second concurrent connection is refused with an error/busy frame instead of opening. Dead engine → a clean error/engine_unavailable frame, never a raw disconnect.

Browser profile v2 (additive)

Four more endpoints extend the same import flow with a durable, pre-provisioned "browser profile" instead of leasing a proxy at import time. They live behind the browser_profile_v2_api_enabled setting — when it's off, all four return a plain 404, not a 403 (no capability hint is leaked while the feature is dark).

EndpointMethodPurpose
/api/connect/browser-profiles/draftsPOSTAllocate a draft profile for a connect_attempt_id + device installation.
/api/connect/browser-profiles/{id}/preparePOSTBind the draft to a platform account and start its sync run.
/api/connect/browser-profiles/{id}/proxy-leasePOSTLease the profile's own bound proxy (no substitution).
/api/connect/browser-profiles/identity-receiptPOSTActivate a profile from a signed engine identity receipt (X-Engine-Receipt-Signature header).

prepare returns the profile plus platform_account_id, governor_id, sync_run_id, sync_state, account_created. Errors across this family are mostly 409 (a conflicting profile/account state) and 503 (the platform request-limit governor isn't configured) — 422 is not used here, bodies are strict Pydantic models that FastAPI itself rejects on shape mismatch.

What's next

For what happens after a session lands — sessions, activity, and the sync engine reporting progress — see API — Browser runtime.

On this page