Skip to content

Connecting Accounts

How to connect social media accounts using OAuth flows, headless mode, and non-OAuth platforms

Before you can post to a platform, you need to connect a social media account to a profile. Zernio supports 14 platforms, each with its own connection method.

OAuth Flow (Most Platforms)

Most platforms use OAuth. The basic flow is:

  1. Call GET /v1/connect/{platform} with your profileId
  2. The API returns an authUrl
  3. Redirect the user to that URL to authorize
  4. After authorization, the user is redirected back to your redirect_url
  5. The account is connected
typescript
const { authUrl } = await zernio.connect.getConnectUrl({
  platform: 'twitter',
  profileId: 'prof_abc123',
  redirectUrl: 'https://myapp.com/callback'
});
// Redirect user to authUrl
python
result = client.connect.get_connect_url(
    platform="twitter",
    profile_id="prof_abc123",
    redirect_url="https://myapp.com/callback"
)
# Redirect user to result.auth_url
bash
curl "https://zernio.com/api/v1/connect/twitter?profileId=prof_abc123&redirect_url=https://myapp.com/callback" \
  -H "Authorization: Bearer YOUR_API_KEY"

See the Start OAuth endpoint for full parameter details.

Platforms Requiring Secondary Selection

Some platforms require an extra step after OAuth - the user needs to select which page, organization, or board to connect:

PlatformWhat to SelectEndpoints
FacebookPageList PagesSelect Page
LinkedInOrganization or PersonalList OrgsSelect Org
PinterestBoardList BoardsSelect Board
Google BusinessLocationList LocationsSelect Location
SnapchatPublic ProfileList ProfilesSelect Profile

Standard vs Headless Mode

Standard mode (default): Zernio hosts the selection UI. The user picks their page/org in Zernio's hosted interface, then gets redirected to your redirect_url.

Headless mode: You build your own branded selection UI. Pass headless=true when starting the OAuth flow. After OAuth completes, the user is redirected to your redirect_url with tempToken, userProfile (URL-encoded JSON), step=select_page, and connect_token query params. Your backend then forwards those into the list and select endpoints to finalize the connection.

The Node SDK (@zernio/node) does not yet expose connect.listFacebookPages / connect.selectFacebookPage. Use the Python SDK or hit the endpoints directly via fetch.

python
# 1. Start the connect flow, returns the OAuth URL.
result = client.connect.get_connect_url(
    platform="facebook",
    profile_id="prof_abc123",
    headless=True,
    redirect_url="https://your-app.com/cb",
)
# Redirect the end-user's browser to result["authUrl"].

# 2. Meta redirects the end-user to your redirect_url with these query
#    params: profileId, tempToken, userProfile (URL-encoded JSON),
#    platform=facebook, step=select_page, connect_token.

# 3. Your backend lists the user's pages.
pages = client.connect.list_facebook_pages(
    profile_id="prof_abc123",
    temp_token="<from step 2>",
)

# 4. Your backend posts the chosen pageId. user_profile must be the
#    DECODED dict, json.loads(urllib.parse.unquote(...)) the value
#    you got in step 2.
result = client.connect.select_facebook_page(
    profile_id="prof_abc123",
    page_id=pages["pages"][0]["id"],
    temp_token="<from step 2>",
    user_profile=json.loads(urllib.parse.unquote("<from step 2>")),
    redirect_url="https://your-app.com/final-success",
)
# Redirect the browser to result["redirect_url"]; result["account"]["accountId"]
# is the SocialAccount ID for the connected page.
bash
# 1. Start the connect flow, returns the OAuth URL.
curl "https://zernio.com/api/v1/connect/facebook?profileId=prof_abc123&headless=true&redirect_url=https://your-app.com/cb" \
  -H "Authorization: Bearer YOUR_API_KEY"
# → 200 { "authUrl": "https://www.facebook.com/...", "state": "..." }
# Redirect the end-user's browser to authUrl.

# 2. Meta redirects the end-user to your redirect_url with profileId,
#    tempToken, userProfile (URL-encoded JSON), platform=facebook,
#    step=select_page, and connect_token query params.

# 3. Your backend lists the user's pages.
curl "https://zernio.com/api/v1/connect/facebook/select-page?profileId=prof_abc123&tempToken=TEMP_TOKEN" \
  -H "Authorization: Bearer YOUR_API_KEY"
# → 200 { "pages": [{ "id": "...", "name": "...", ... }] }

# 4. Your backend posts the chosen pageId. userProfile must be the
#    decoded JSON object, not the URL-encoded string.
curl -X POST "https://zernio.com/api/v1/connect/facebook/select-page" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "prof_abc123",
    "pageId": "123456789",
    "tempToken": "TEMP_TOKEN",
    "userProfile": { "id": "1234567890", "username": "...", "displayName": "..." },
    "redirect_url": "https://your-app.com/final-success"
  }'
# → 200 {
#   "redirect_url": "https://your-app.com/final-success?connected=facebook&profileId=...&username=...",
#   "account": { "accountId": "<fb_id>", "platform": "facebook", ... }
# }
# Redirect the browser to redirect_url.

Connecting Meta Ads only (skip the Page picker)

If your end-user only needs ads (audience uploads, Conversions API, analytics, list ads/campaigns), you can auto-pick the first Page in your backend without ever rendering a picker. The end-user transitions from Meta's OAuth screen directly to your "Connected" screen.

The flow is identical to the headless flow above, but uses /v1/connect/facebook/ads to start and your backend skips any UI step. Roughly 70% of the metaads surface (everything that doesn't emit object_story_spec.page_id) works regardless of which Page is bound, so picking the first one is fine for ads-only callers.

The remaining 30%, boost Page posts, Click-to-WhatsApp ads, Lead Gen Forms, IS Page-specific. If your end-user later needs those for a specific Page, surface a picker at that point and POST a new pageId to /v1/connect/facebook/select-page. Our handler updates the existing Facebook account in place, no re-OAuth.

Scoping sync to specific ad accounts

By default, sync covers every act_* ad account the connected Meta token can see. That's fine for solo accounts but causes leakage for users in agencies or multi-Business-Manager setups (the token sees every account in every BM the user has a role on). To restrict sync to a specific allowlist, pass adAccountId (single) or adAccountIds (multiple) on GET /v1/connect/facebook/ads:

?adAccountId=act_1330190928038136
?adAccountIds=act_1330190928038136,act_3686966528111132

Each ID is validated against the connected token's /me/adaccounts and persisted server-side. The account.ads.initial_sync_completed webhook then carries account.platformAdAccountId (when scope is exactly one) and account.platformAdAccountIds (always) so you can confirm what was synced. Omit both params to keep the legacy "sync everything visible" behavior. Latest call wins; a subsequent connect with new IDs replaces the prior allowlist.

The Node SDK (@zernio/node) does not yet expose connect.listFacebookPages / connect.selectFacebookPage. Use the Python SDK or hit the endpoints directly via fetch for the headless ads-connect flow.

python
# 1. Start the ads connect flow, returns the OAuth URL.
result = client.connect.connect_ads(
    platform="facebook",
    profile_id="prof_abc123",
    headless=True,
    redirect_url="https://your-app.com/cb",
)
# Redirect the end-user's browser to result["authUrl"].

# 2. End-user completes Meta OAuth. Browser lands at your redirect_url
#    with profileId, tempToken, userProfile, step=select_page, etc.

# 3. Your backend lists pages and auto-picks the first one, no UI shown.
pages = client.connect.list_facebook_pages(
    profile_id="prof_abc123",
    temp_token="<from step 2>",
)

result = client.connect.select_facebook_page(
    profile_id="prof_abc123",
    page_id=pages["pages"][0]["id"],
    temp_token="<from step 2>",
    user_profile=json.loads(urllib.parse.unquote("<from step 2>")),
    redirect_url="https://your-app.com/final-success",
)
# Redirect the browser to result["redirect_url"]. The metaads
# SocialAccount is created alongside the Facebook account.
bash
# 1. Start the ads connect flow, returns the OAuth URL.
curl "https://zernio.com/api/v1/connect/facebook/ads?profileId=prof_abc123&headless=true&redirect_url=https://your-app.com/cb" \
  -H "Authorization: Bearer YOUR_API_KEY"
# → 200 { "authUrl": "...", "state": "..." }

# 2. End-user OAuths. Browser lands at your redirect_url with the
#    standard headless params (profileId, tempToken, userProfile, etc.).

# 3. Your backend lists pages and auto-picks the first one.
curl "https://zernio.com/api/v1/connect/facebook/select-page?profileId=prof_abc123&tempToken=TEMP_TOKEN" \
  -H "Authorization: Bearer YOUR_API_KEY"

curl -X POST "https://zernio.com/api/v1/connect/facebook/select-page" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "prof_abc123",
    "pageId": "FIRST_PAGE_ID",
    "tempToken": "TEMP_TOKEN",
    "userProfile": { "id": "...", "username": "...", "displayName": "..." },
    "redirect_url": "https://your-app.com/final-success"
  }'
# Redirect the browser to the response's redirect_url. The metaads
# SocialAccount is created alongside the Facebook account.

Non-OAuth Platforms

Bluesky

Bluesky uses app passwords instead of OAuth:

The Node SDK (@zernio/node) does not yet expose connect.connectBlueskyCredentials. Use the Python SDK or hit the endpoint directly via fetch.

python
account = client.connect.connect_bluesky_credentials(
    identifier="yourhandle.bsky.social",
    app_password="your-app-password",
    state="profile_id=prof_abc123",
)
print(f"Connected: {account['_id']}")
bash
curl -X POST "https://zernio.com/api/v1/connect/bluesky/credentials" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "yourhandle.bsky.social",
    "appPassword": "your-app-password",
    "state": "profile_id=prof_abc123"
  }'

See Connect Bluesky for details.

Telegram

Telegram uses an access code flow:

  1. Call POST /v1/connect/telegram to initiate the connection and get an access code
  2. The user sends this code to the Zernio Telegram bot
  3. Poll GET /v1/connect/telegram to check the status until connected

Managing Connected Accounts

After connecting, you can:

  • List all accounts - see all connected accounts
  • Update an account - change settings like default pages or boards
  • Check account health - verify tokens and permissions are valid
  • Disconnect an account - remove a connection

Updating Selections After Connection

You can change the selected page, organization, or board on an existing connection without re-authenticating:

  • Update Facebook Page
  • Update LinkedIn Organization
  • Update Pinterest Board
  • Update GMB Location
  • Update Reddit Subreddit