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.
These endpoints issue and refresh the device-bound grant that every Browser
runtime call is authenticated
against. Mounted by build_browser_native_authorization_router and
build_browser_native_management_router (app/products/platform/api/ browser_native_authorization.py) plus build_browser_engine_receipt_router
(browser_engine_receipt.py), all composed via compose_browser_native
(app/main.py:126-130).
Not a full protocol spec
This page describes the contract an integrator needs — paths, methods, error codes. It does not reproduce field-level signature and challenge formats; those are treated the same as the rest of our security posture, described at the results level. See Security for that framing.
Authorization code exchange
| Endpoint | Method | Notes |
|---|---|---|
/api/auth/browser-native/authorize | GET | Shows the external consent page. Exists only when the routes_enabled policy flag is on. |
/api/auth/browser-native/authorize | POST | Records the consent decision, issues the authorization code, 303-redirects back to the app with code and state. |
/api/browser-native/v1/consent/context | POST | Same consent dialog, shown inside the panel instead of via an external navigation — added after Cloudflare's SSRF rule blocked the external page's redirect to a 127.0.0.1 callback. |
/api/browser-native/v1/consent | POST | Records the decision for the in-panel flow. |
/api/browser-native/v1/token | POST | Exchanges the code for a grant. Accepts either the current encoded envelope ({"request": "<base64url>"}) or the older flat body — both resolve to the same exchange, so existing installed builds keep working. |
Both consent paths share the same boundary checks (exact-origin request
validation, device-signature verification, replay protection on the code) —
the in-panel path is not a weaker shortcut, just a different transport for the
same dialog. A replayed authorization code returns 409 authorization_operation_replayed; a denied consent redirects back with
error=access_denied rather than an HTTP error.
Challenges, refresh, revoke
POST /api/browser-native/v1/challenges— issues a signed challenge the app uses to build its next proof. Same shape as/challengeson the Browser runtime page — two issuance points for the same primitive, one per phase.POST /api/browser-native/v1/refresh— refreshes the grant, proof-authenticated.POST /api/browser-native/v1/revoke— revokes the current installation, proof-authenticated.GET /api/browser-native/v1/config— public bootstrap config for the desktop app (no auth).
All three of challenges/refresh/revoke fail closed to a single generic
invalid_grant error rather than distinguishing why a grant failed —
intentional, not an oversight: the failure reason is not handed back over the
wire.
Lost-device recovery
POST /api/me/browser-installations/{device_installation_id}/revoke —
revokes an installation using the panel session cookie, not a device
proof. This is the recovery path for a lost or stolen laptop: the person
signs into the panel from anywhere and kills the old installation without
needing the device itself. Returns 404 not_found if the installation
doesn't belong to the caller.
Engine identity receipts
POST /api/browser-native/v1/engine-identity-receipts activates a browser
profile from a signed receipt the sync engine issues, verified via the
X-Engine-Receipt-Signature header. 401 invalid_engine_identity_receipt on
a bad/expired signature; 409 (via the shared connect-family error mapping)
on a conflicting activation state.
A second, separate mount for the same receipt shape
POST /api/connect/browser-profiles/identity-receipt (documented on API —
Connecting an account) accepts the
same EngineReceiptBody and the same signature header, but is a
distinct route reached through the connect flow rather than this one. Check
the full path, not just the body shape, when reading a log line.
What's next
Once a grant is issued, see API — Browser runtime for what the app does with it.
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.
API — Fansly sync
The endpoint family the Fansly collection engine itself calls to accept work, claim and report on sync segments, and prove session liveness. Not called by the panel, the extension, or Tease Browser.