TEASEDocs
ProductsAccount Platform

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.

These are server-to-server endpoints for the Fansly collection engine, not client endpoints. Auth is a service signature (X-Engine-Signature header, verified against a shared service secret via authorize_engine_service_request) — not a device proof and not a bearer connect token, both used elsewhere in this API. A bad or missing signature returns 401 engine_unauthorized.

Mount point is conditional and non-obvious

This router only exists when settings.fansly_engine_enabled is true. It is built by build_fansly_engine_sync_router() and appended to the router list inside browser_native_composition.py:361-373 — there is no include_router(fansly_engine_sync...) call anywhere by that name; the mount is three hops deep (app/main.py:127compose_browser_native → the conditional append → main.py:129-130's generic loop). Grepping for the router variable name will not find the mount point — trace the call chain instead.

All paths below are relative to the router's own prefix: /api/platform-engine/v1/fansly (fansly_engine_sync.py:207).

A parallel surface, not the desktop one

/api/browser-runtime/v1/sync/* (documented on API — Browser runtime) uses the same segment vocabulary — claim, renew, release, permit, result, outcome — for the desktop app's view of sync. This page is the same vocabulary for the engine's view, under a completely different prefix and a completely different auth mechanism. They are not aliases of each other.

Commands and segments

EndpointPurpose
POST /sync/commands/acceptEngine accepts a dispatched sync command.
POST /sync/segments/claimClaims a page/segment of work.
POST /sync/segments/renewRenews a claim before it expires.
POST /sync/segments/releaseReleases a claim without completing it.
POST /sync/segments/permitRequests the request-governor permit for a segment.
POST /sync/segments/resultReports the result of a completed segment.
POST /sync/segments/outcomeReports the final outcome for a segment.

A conflicting or busy claim returns 409 engine_sync_conflict (retryable conditions are signaled the same way — the engine is expected to back off and retry, not treat every 409 as terminal). A lock-contention timeout on the underlying receipt table also surfaces as 409, not a raw 500.

Response version field is deliberately rewritten

Response bodies are built by the same projection code as the desktop transport, which stamps version: "browser-platform-sync-v1". This router rewrites that field to "fansly-engine-sync-v1" (ENGINE_SYNC_VERSION) before returning. The gateway checks this field strictly — losing the rewrite once made every response product_sync_response_invalid and silently stopped all collection (permits were granted, zero pages collected). If you see that error, this is the first thing to check.

Session liveness

POST /sync/session/liveness — the engine reports whether the platform account's session is still usable: platform_account_id, credential_generation, a verdict, reason, and the previous_verdict. This is the authoritative signal behind "connected in the database" versus "actually collecting" — see Troubleshooting.

Raw evidence reconciliation

POST /sync/raw/reconcile — reconciles a batch of raw response entries against the durable raw object store (sync_raw_object_store.py + platform_sync_raw_snapshots). See Connections for who else reads that store.

What's next

For the desktop app's own, differently-authenticated sync surface, see API — Browser runtime.

On this page