Zernio
Zernio
Overview

Guides

ProfilesConnecting AccountsMedia UploadsQueue SchedulingTimezones & SchedulingIdempotency & Safe RetriesPost LifecycleError HandlingRate LimitsPlatform Settings
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Guides

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 16 posting platforms plus Shopify, 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
const { data } = await zernio.connect.getConnectUrl({
  path: { platform: 'twitter' },
  query: {
    profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
    redirect_url: 'https://myapp.com/callback'
  }
});
// Redirect user to data.authUrl

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 Pages → Select Page
LinkedInOrganization or PersonalList Orgs → Select Org
PinterestBoardList Boards → Select Board
Google BusinessLocationList Locations → Select Location
SnapchatPublic ProfileList Profiles → Select Profile
Instagram (loginMethod=facebook_login only)Page with a linked Instagram accountList Pages → Select Account

Instagram is the only conditional row: the default Instagram Login flow (loginMethod omitted) creates the account directly with no selection step. Only loginMethod=facebook_login adds one, because the user has to say which Page to connect. See Instagram for the difference between the two methods. Both modes below work for it.

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 step value tells you which selection endpoint to call next: select_page (Facebook), select_organization (LinkedIn), select_board (Pinterest), select_location (Google Business), select_public_profile (Snapchat), select_phone_number (WhatsApp), select_account (Instagram via Facebook Login). Instagram sends no userProfile, since its select-account endpoint doesn't take one.

import Zernio from '@zernio/node';

const zernio = new Zernio();

// 1. Start the connect flow, returns the OAuth URL.
const { data: start } = await zernio.connect.getConnectUrl({
  path: { platform: 'facebook' },
  query: {
    profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
    headless: true,
    redirect_url: 'https://your-app.com/cb',
  },
});
// Redirect the end-user's browser to start.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.
const { data: pages } = await zernio.connect.facebook.listFacebookPages({
  query: { profileId: '66a1f0c2a4b9d3e8f1a2b3c4', tempToken: '<from step 2>' },
});

// 4. Your backend posts the chosen pageId. userProfile must be the
//    DECODED object: JSON.parse(decodeURIComponent(...)) the value
//    you got in step 2.
const { data: result } = await zernio.connect.facebook.selectFacebookPage({
  body: {
    profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
    pageId: pages.pages[0].id,
    tempToken: '<from step 2>',
    userProfile: JSON.parse(decodeURIComponent('<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.

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.

import Zernio from '@zernio/node';

const zernio = new Zernio();

// 1. Start the ads connect flow, returns the OAuth URL.
const { data: start } = await zernio.connect.connectAds({
  path: { platform: 'facebook' },
  query: {
    profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
    headless: true,
    redirect_url: 'https://your-app.com/cb',
  },
});
// Redirect the end-user's browser to start.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.
const { data: pages } = await zernio.connect.facebook.listFacebookPages({
  query: { profileId: '66a1f0c2a4b9d3e8f1a2b3c4', tempToken: '<from step 2>' },
});

const { data: result } = await zernio.connect.facebook.selectFacebookPage({
  body: {
    profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
    pageId: pages.pages[0].id,
    tempToken: '<from step 2>',
    userProfile: JSON.parse(decodeURIComponent('<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.

Non-OAuth Platforms

Bluesky

Bluesky uses app passwords instead of OAuth:

import Zernio from '@zernio/node';

const zernio = new Zernio();

// state is "{userId}-{profileId}": userId from GET /v1/users
// (currentUserId), profileId from GET /v1/profiles.
const { data } = await zernio.connect.bluesky.connectBlueskyCredentials({
  body: {
    identifier: 'yourhandle.bsky.social',
    appPassword: 'your-app-password',
    state: 'USER_ID-PROFILE_ID',
  },
});
console.log(`Connected: ${data.account._id}`);

See Connect Bluesky for details.

Telegram

Telegram uses an access code flow:

  1. Call GET /v1/connect/telegram to generate an access code (valid 15 minutes)
  2. The user adds the Zernio Telegram bot as an admin and sends it the code
  3. Poll PATCH /v1/connect/telegram to check the status until connected

If the bot is already an admin of the channel or group, you can skip the code flow and connect directly with POST /v1/connect/telegram and the chat ID.

Shopify

Shopify is OAuth, but the authorization URL is built per store, so you must know the merchant's myshopify.com domain before you start. Shopify offers no store picker and no lookup from a merchant to their shops.

  1. Collect the store domain from the merchant (your-store.myshopify.com; the bare your-store prefix is accepted)
  2. Call GET /v1/connect/shopify with profileId and shop to get the authorization URL
  3. Redirect the merchant to the returned authUrl; after approval they land on your redirect_url

A merchant who installs from the Shopify App Store never types the domain, because Shopify supplies it to Zernio directly.

If you would rather avoid the browser flow, the merchant can create a custom app in their Shopify admin with the read_content and write_content scopes and hand you its Admin API access token, which you exchange for a connection via POST /v1/connect/shopify/token. The shop domain is required there too: an Admin token does not identify its own store.

A connected store publishes no social posts. It powers the Blogs API. See the Shopify guide.

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
Was this page helpful?

Profiles

What a profile is, why profiles are free, the one-account-per-platform rule, and how to connect a second account of the same platform from the dashboard or the API

Media Uploads

How to upload images, videos, and documents for use in posts

On this page

OAuth Flow (Most Platforms)Platforms Requiring Secondary SelectionStandard vs Headless ModeConnecting Meta Ads only (skip the Page picker)Scoping sync to specific ad accountsNon-OAuth PlatformsBlueskyTelegramShopifyManaging Connected AccountsUpdating Selections After Connection