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:
- Call
GET /v1/connect/{platform}with yourprofileId - The API returns an
authUrl - Redirect the user to that URL to authorize
- After authorization, the user is redirected back to your
redirect_url - The account is connected
const { authUrl } = await zernio.connect.getConnectUrl({
platform: 'twitter',
profileId: 'prof_abc123',
redirectUrl: 'https://myapp.com/callback'
});
// Redirect user to authUrlresult = client.connect.get_connect_url(
platform="twitter",
profile_id="prof_abc123",
redirect_url="https://myapp.com/callback"
)
# Redirect user to result.auth_urlcurl "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:
| Platform | What to Select | Endpoints |
|---|---|---|
| Page | List Pages → Select Page | |
| Organization or Personal | List Orgs → Select Org | |
| Board | List Boards → Select Board | |
| Google Business | Location | List Locations → Select Location |
| Snapchat | Public Profile | List Profiles → Select 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.
# 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.# 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_3686966528111132Each 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.
# 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.# 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.
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']}")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:
- Call
POST /v1/connect/telegramto initiate the connection and get an access code - The user sends this code to the Zernio Telegram bot
- Poll
GET /v1/connect/telegramto 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: