What changes
What moves
What is removed
Concept map
Permissions to scopes
Each migrated connection keeps its own V2 permissions, mapped to V3 scopes. Nothing is widened, and there is no review wait.
The extra
queue:* and users:read scopes cover operations those V2 permissions already allowed. V3 lists scheduled posts and queued mass messages under queue:*, and blocked and restricted users under users:read.
Route map
V3 Access operations live under/v3/access. Authenticate with Authorization: Bearer <access_token> instead of apikey, and send the same account id in the x-linked-account-id header.
These V2 routes call the same OnlyFans operation as the listed V3 operation.
Profile and notifications
Chats and mass messages
Subscribers and subscriptions
Users and lists
Posts and stories
Vault
Uploads use a different flow in V3:
POST /v3/access/uploads/start, PUT /v3/access/uploads/:mediaUploadId/parts/:partNumber, then PUT /v3/access/uploads/:mediaUploadId/finalize or POST /v3/access/uploads/finalize (vault:write). These operations are not a one-to-one match for the V2 upload routes (POST /v2/access/uploads/init, PUT /v2/access/uploads/:mediaUploadId/parts/:partNumber, PUT /v2/access/uploads/:mediaUploadId, and POST /v2/access/uploads/complete).
Earnings and analytics
Promotions
Vault+
V2 Vault+ routes move from/v2/vault-plus/* to /v3/vault-plus/* with the same route table. Authenticate with Authorization: Bearer <access_token> and send the account id in x-linked-account-id.
Vault+ must be on for the environment, or V3 returns
403 vault_plus_disabled. It must also be on for the account, or add and sync return 403 linked_account_vault_plus_disabled. Turn it on or off per account with linked account settings.
Not yet available on V3
These V2 routes have no V3 operation yet:GET /v2/access/stories/mapGET /v2/access/stories/itemsPOST /v2/access/uploads/checkGET /v2/access/promotions/tracking-links/:trackingLinkIdPUT /v2/access/promotions/tracking-links/:trackingLinkIdGET /v2/access/promotions/trial-links/:trialLinkIdDELETE /v2/access/promotions/trial-links/:trialLinkIdPUT /v2/access/promotions/:promotionIdPUT /v2/access/promotions/bundles/:bundleId
Prepare before cutover
1
Save your Live callback and client secret
The migration flow pre-fills Live callbacks from your V2 redirect URLs. Review them, then save the Live client secret where your app runs. It is shown once.
2
Build the V3 flow for new users
New users connect through V3 OAuth. Follow Authorize and Tokens and refresh. The authorization URL replaces
POST /v2/link/init, and your callback receives code and state instead of a connection id. Store each user’s refresh token, encrypted at rest, with its authorization_id, and persist the rotated refresh token on every refresh.3
Rehearse in Test
Get a Test token and make one successful Access call with your Test client.
4
Make a successful Live token request
Get at least one token with your Live client ID and current client secret.
5
Update your webhook receivers
V3 signs deliveries with the Standard Webhooks scheme, which is the same scheme V2 used. Only the header names change:
Receivers that verify with a Standard Webhooks library keep working with the same
whsec_ secret. Receivers that read svix-* headers by name must change before cutover. Event names and payloads also change. See Webhook events.6
Handle the V2 migration response
Add the account switch described in Switch each account. It runs after cutover, but it must be deployed before.
Verify V3 webhooks
Until cutover, V2 still sendssvix-* headers. This Node.js receiver accepts both header sets, so you can deploy it before cutover:
After cutover on V2
Every V2 response for a migrated connection is401 with this body:
requestId. Match on error being Connection has migrated.
Live V2 API keys of a migrated organization keep answering this 401 for 90 days after cutover. After that they stop working and V2 rejects them as invalid keys. Remove them from your configuration once every account is on V3.
Switch each account
When V2 answersConnection has migrated for an account, exchange that account id once for V3 tokens. Store the tokens, then call V3 with the same id.
Exchange the account id
Call the token endpoint with your Live client credentials, using HTTP Basic authentication (client_secret_basic):
The endpoint also accepts
client_id, which must match the Basic credentials, and resource, which you do not need. If you send resource, it must be https://api.ofauth.com/v3. Any other parameter returns invalid_request.
linked_accounts.data[0].id is the account id you exchanged. Access tokens last 10 minutes. Refresh them with grant_type=refresh_token at the same endpoint, and store the new refresh token every time. Refresh tokens rotate on every use, and replaying a used refresh token revokes the whole authorization.
Exchange rules
- Each account can be exchanged once.
- The exchange is available for 90 days after cutover.
- Only accounts migrated from your organization can be exchanged, and only with your Live client.
- Each Live client can make 120 exchange requests per minute. Above that, the endpoint returns
429with aRetry-Afterheader. - Make sure only one worker exchanges a given account. Two parallel exchanges for the same id leave one with
invalid_grant. - If you lose the tokens after the exchange, send the creator through authorization again.
Errors
Call V3 with the same id
cURL
{ "error": "...", "recovery": { "action": "..." }, "requestId": "..." }. The account errors:
See V3 errors for every code.
Error code mapping
Manage linked accounts
These routes take the bearer access token. They replaceGET /v2/account/connections and the V2 connection routes for your app.
Revoke tokens
token (and optionally token_type_hint) with your client credentials, using HTTP Basic authentication. Revoking a refresh token ends that session only. Revoking the last active session ends the whole authorization and sends authorization.revoked with reason: "oauth_revocation". Unknown tokens still return 200.
To remove one account but keep the rest, call DELETE /v3/linked-accounts/{id} instead.
Group accounts under one person
When a creator or agency reconnects through your integration, you can group the other migrated accounts that belong to the same person. Addlinked_account_ids to the Live authorization request:
After the person reconnects, the listed accounts are grouped under the person who reconnects. Each account keeps its id, its scopes, and your tokens for it. A malformed list, more than 25 ids, or the parameter on a non-Live client returns
invalid_request.
Grouping sends no webhook events.
Webhook events
V2connection.* events become V3 lifecycle events. The migration maps each V2 endpoint filter like this:
Lifecycle events are on or off per webhook destination (
lifecycleEventsEnabled). A migrated endpoint gets them if its V2 filter had any connection.* event or no filter at all. Realtime turns on if the filter had any Realtime event or no filter at all.
Only enabled V2 endpoints with a plain HTTPS URL (no query string, fragment, or credentials) move. An endpoint whose filter had only rules.updated is not moved.
Payloads carry ids only.
data has authorization_id, reason, and, for linked_account.* events, linked_account_id. authorization.* events list the account ids in linked_account_ids, or in added_linked_account_ids and removed_linked_account_ids (plus scopes_changed) for authorization.updated. Above 100 ids the list is replaced by linked_account_ids_truncated: true, linked_account_count, and, while the authorization still works, linked_accounts_url: "/v3/linked-accounts". authorization.revoked never carries linked_accounts_url. Lifecycle deliveries are not billed. See V3 webhooks.
Every delivered event, lifecycle or Realtime, has this envelope:
linkedAccountId is null for authorization.* events. Deliveries use the Standard Webhooks headers webhook-id, webhook-timestamp, and webhook-signature, signed with the same whsec_ secret.
Realtime over WebSocket
Openwss://v3-realtime.ofauth.com/v3/realtime?linkedAccountId=<account id> with Authorization: Bearer <access_token>. Each message has eventId, type, schemaVersion, order, receivedAt, environment, integrationId, linkedAccountId, test, and data. The socket carries Realtime events only; lifecycle events arrive by webhook.
Authorization server discovery
The authorization server metadata is at:https://auth.ofauth.com/link. The document lists the authorize, token, and revoke endpoints and the JWKS URL.
Pricing
Migrating organizations get 30% off the first month of the V3 plan they choose. The Developer Build plan keeps its own launch discount instead; the two discounts do not combine. The offer has no end date. V2 billing stops at cutover.FAQ
Do creators need to log in to OnlyFans again?
Do creators need to log in to OnlyFans again?
No. Migrated accounts keep their OnlyFans session. Creators only reconnect if OnlyFans ends the session, as on V2.
Can I keep my V2 connection ids?
Can I keep my V2 connection ids?
Yes. Every migrated account keeps its id. Send it in
x-linked-account-id on V3.What replaces the raw proxy?
What replaces the raw proxy?
Typed V3 Access operations. If an OnlyFans path you rely on has no operation, contact support@ofauth.com with the path.
How long do tokens last?
How long do tokens last?
Access tokens last 10 minutes. Refresh tokens last 90 days and are replaced on every refresh. If a session is unused for 90 days, the user authorizes again.
What happens if I lose a refresh token?
What happens if I lose a refresh token?
Send the user through authorization again. They see the accounts and scopes they already approved, and you get a new session in the same authorization. For a migrated account whose exchanged tokens you lost, the same applies.
Does Sandbox behave like V2 Sandbox?
Does Sandbox behave like V2 Sandbox?
Yes for data: deterministic fixtures and no OnlyFans calls. The difference is the flow: you authorize with your Sandbox client instead of a
sk_sandbox_ key.Can I migrate one client app at a time?
Can I migrate one client app at a time?
No. The whole organization moves together: every Live client app and every Live connection. V2 Sandbox apps, keys, and connections are not migrated. Set up V3 Sandbox and Test fresh.