Skip to content

Connect a Facebook Page

The platform can publish approved social drafts to a Facebook Page the same way it posts to X and LinkedIn: a draft targets a connected Page, and the publish worker posts it via the Meta Graph API. This guide covers the one-time Meta-app setup that unlocks it, how a user connects a Page, and — most importantly — how this works when your own clients (clubs, festivals, …) need to publish to their Pages.

The multi-tenant question (read this first)

Section titled “The multi-tenant question (read this first)”

Do your clients each need to create their own Meta app? No. The recommended and standard model — the one Buffer, Hootsuite, Later, and Mixpost all use — is:

One Meta app, owned by you (the platform operator), serves every client’s Page. Each client signs in with Facebook, grants access to their own Page, and the platform stores a per‑Page token for that tenant. A client only needs to be an admin of their own Facebook Page — no developer account, no app, no business verification on their side.

This is exactly how the platform is wired: the Meta app credentials live in server env (FACEBOOK_CLIENT_ID / FACEBOOK_CLIENT_SECRET / …), and every connected Page token is stored on the connecting user’s own owner‑scoped channel. One app, many tenants, per‑tenant tokens.

Shared app (recommended)Per‑tenant app (BYO)
Who creates the Meta appYou, onceEvery client
Client setupClick “Connect Facebook”, pick a PageCreate a developer app, configure login, paste secrets
App Review / Business VerificationYou do it once (see below)Each client avoids review by staying at Standard Access
Fits non‑technical clients✅ Yes❌ No (clubs won’t do this)
OAuth consent showsyour brand (“Viite is requesting…”)the client’s brand
Rate limitsPer‑Page (scales fine — see Rate limits)Per their own app
Status in code✅ Implemented (server env creds)🔜 Not wired (would follow the Gmail BYO‑creds channel pattern)

The rest of this guide is your one‑time setup as the platform operator.


  • A Facebook account registered as a Meta developer (accept the platform terms; verify by phone/email).
  • A Meta Business portfolio (the rebranded “Business Manager,” at business.facebook.com). The create‑app wizard prompts you to attach one, and Business Verification is performed on the portfolio, not the app. You can skip attaching it for pure local testing, but you’ll need it before real clients can connect.
  • A Facebook Page you administer, to test against (publishing to Pages you admin needs no App Review — see Access levels).
  • For production: a legally registered business with documents for Business Verification.

Step 1 — Create the Meta app (2025/2026 “use case” flow)

Section titled “Step 1 — Create the Meta app (2025/2026 “use case” flow)”

Meta replaced the old “pick a type → add products → add permissions” wizard with a use‑case model: you choose what the app should do and Meta auto‑adds the products, permissions, and APIs.

  1. Open the App DashboardCreate app.
  2. Name the app and confirm a contact email.
  3. On “What do you want your app to do?”, pick the Page‑management use case — currently surfaced as “Manage everything on your Page.” If no Page tile fits, choose “Other”, which routes you to the classic type picker → select Business. Both paths converge on the same capability and provision Facebook Login for Business + the Pages permission set.
  4. Attach your Business portfolio when prompted (you may defer this for early local testing, but it’s required before App Review).
  5. Create. You land in the dashboard with the use case configurable under Use cases (each has its own Permissions and Settings tabs).

Docs: Create an App · App Dashboard · Pages API

Step 2 — Configure Facebook Login for Business

Section titled “Step 2 — Configure Facebook Login for Business”

This is the modern, asset‑oriented login product (vs. classic “log in a consumer”). It is driven by a named configuration rather than an ad‑hoc scope string.

  1. In the app, open Facebook Login for Business → Configurations → Create configuration.

  2. Set the access token type to User access token.

  3. Select the asset type the app needs: Pages.

  4. Add the permissions (next step) to the configuration.

  5. Save — Meta returns a Configuration ID (config_id). Copy it; it becomes the FACEBOOK_LOGIN_CONFIG_ID env var. The connect flow then passes config_id (and omits scope, per Meta’s recommendation).

  6. Open Facebook Login for Business → Settings → Client OAuth Settings and add your Valid OAuth Redirect URI (HTTPS only):

    https://platform-api.viite.ai/api/connect/facebook/callback

    It must match the FACEBOOK_OAUTH_REDIRECT_URI env var byte‑for‑byte. For local testing add http://localhost:4000/... (or your API port) too.

Docs: Facebook Login for Business · Manual login flow

Add these to the use case’s Permissions tab (and to the FLB configuration):

PermissionWhyDefault
public_profileBasic identityauto‑granted
pages_show_listList the user’s Pages (/me/accounts → Page tokens)review for Advanced Access
pages_read_engagementRead Page data (dependency of posting)review for Advanced Access
pages_manage_postsCreate/edit/delete Page posts (the publish grant)review for Advanced Access

Request only these — App Review rejects permissions it can’t tie to a visible feature. (Add pages_manage_engagement later only if you want to manage comments/the “first comment.”)

Docs: Permissions Reference

App Settings → Basic: the App ID is shown plainly (it’s the OAuth client_id); the App Secret is behind a Show button (re‑auth required). Treat the secret as server‑side only. Regenerating it immediately invalidates the old one, so rotate deliberately.


Meta gates each permission by access level, independent of Development/Live mode:

  • Standard Access (no App Review): the permission works only for users who hold a role on the app — Admins, Developers, Testers — and who admin the target Page. This is enough to fully build, test, and dogfood publishing to your own Pages, and to run early pilots by adding clients as app Testers. The posts are real and public.
  • Advanced Access (App Review + Business Verification): required the moment real clients who are not app‑role‑holders connect their own Pages. This is what makes the shared‑app model work for the public.

Docs: Access levels · App modes · App roles

Going to production: App Review + Business Verification

Section titled “Going to production: App Review + Business Verification”

To let arbitrary clients connect their Pages, complete both (verification gates the review):

Business Verification — on your Business portfolio (Business Settings → Security Center / Business Info → Verify):

  • Legal business registration or tax document or a utility bill in the business’s legal name, showing legal name + address (typically dated within 12 months).
  • A verifiable business phone/email; admin 2FA enabled.
  • Typically a few days (blurry/mismatched docs add round‑trips).

App Review — for pages_manage_posts, pages_read_engagement, pages_show_list at Advanced Access:

  • A detailed use‑case description per permission (what it does, why you need it).
  • A screencast of the complete flow: a user logs in via Facebook Login for Business, grants the permission, selects a Page, and your app creates a post — reviewers must see the permission exercised end‑to‑end.
  • Test credentials / a reproducible path.
  • Typically a few days to ~2 weeks; budget 2–4 weeks for verification + review with one rejection cycle.

Docs: App Review · Business Verification (Meta Business Help)


Set these on the API (Coolify). The integration stays dormant until the first three (plus SOCIAL_TOKEN_ENCRYPTION_KEY) are present, so a deploy without them changes nothing.

Env varRequiredNotes
FACEBOOK_CLIENT_IDThe Meta App ID. Non‑secret.
FACEBOOK_CLIENT_SECRETThe Meta App Secret. 🔒 secret — never expose.
FACEBOOK_OAUTH_REDIRECT_URIByte‑for‑byte one of the app’s Valid OAuth Redirect URIs, e.g. https://platform-api.viite.ai/api/connect/facebook/callback.
SOCIAL_TOKEN_ENCRYPTION_KEYShared with X/LinkedIn; encrypts stored tokens at rest.
FACEBOOK_LOGIN_CONFIG_IDoptionalThe Facebook Login for Business configuration id. Set it to pass config_id; leave empty for classic scope‑based login. Non‑secret.
FACEBOOK_PAGE_IDoptionalDisambiguates which Page to connect when the user administers several (else the first Page is chosen).

See Environment variables for the full inventory and the public/secret classification.


Connecting and publishing (in the product)

Section titled “Connecting and publishing (in the product)”

Once the env vars are set:

  1. Dashboard → Settings → Connected social accounts → Facebook Page → Connect. This is a full‑page redirect to Facebook (the OAuth dance needs the callback to land on the API origin). The user authorizes and picks a Page; the platform stores the encrypted Page token on a social://<page>@facebook.com channel they own.
  2. Compose a post targeting that Facebook destination — via the unified content form, or a socialVeille‑style preset whose dispatch destination points at facebook.com.
  3. Approve + schedule the draft in the review queue.
  4. The publish worker picks up the due draft and posts it to the Page — run by “Publish now” on the draft, or by a scheduled custom flow. (The worker is platform‑generic — it routes by the draft’s platform — so no Facebook‑specific schedule is needed.)

Link handling mirrors X/LinkedIn: keep attaches the source URL as a preview card, omit strips it, reply posts the body link‑free and drops the URL in a comment.


A single shared app does not become a single bottleneck, because Page‑token calls are rate‑limited per Page, not per app (the documented budget is on the order of 4,800 calls per engaged user per Page over a rolling 24h window). One heavy client can’t starve another. Read the X-Business-Use-Case-Usage / X-Page-Usage response headers and back off per Page; spread bulk posts. Docs: Rate limiting · Page‑level rate limits.

  • Self‑hosted deployments of the platform (the operator isn’t Viite).
  • A tenant that needs its own brand on the OAuth consent screen, its own rate‑limit bucket, or isolation from Viite’s app policy standing.
  • A tenant that wants to avoid Viite passing review by using Standard Access with their own app (only viable if that tenant is technical).

BYO‑app is not implemented yet. Wiring it means resolving the Meta app creds per org from a channel instead of (or before) the global env — the same shape the Gmail integration already uses for its optional clientId/clientSecret fallback. Until then, the platform is single‑shared‑app.

The same Meta app + Graph host also reaches Instagram (a Business/Creator account linked to a Facebook Page — shares the Page‑token model, different permissions: instagram_basic, instagram_content_publish) and WhatsApp Cloud API (a separate product with its own onboarding and whatsapp_business_messaging permission). They’d be sibling connectors reusing this OAuth layer — FB + IG share most plumbing; WhatsApp is its own auth path. None are wired today.


SymptomLikely cause
Connect redirect errors with redirect_uri mismatchFACEBOOK_OAUTH_REDIRECT_URI doesn’t byte‑for‑byte match a Valid OAuth Redirect URI on the app (scheme, host, trailing slash).
“No Facebook Pages found” on connectThe signed‑in user doesn’t admin any Page, or pages_show_list wasn’t granted. If you’ve confirmed both and it still fails, see When the normal flow can’t see the Page — this is a known Meta bug for Business‑Portfolio‑owned Pages, not a config error.
“Application inactive” on the Facebook OAuth dialogThe app is in Development mode and the Facebook profile you’re logging in with has no App role (Admin/Developer/Tester). App Dashboard → App roles → Roles → Add People.
Business Settings actions (“Assign access”, editing app↔asset links) throw “You don’t have access to take this action”Meta blocks all Business Portfolio management actions until the account has two‑factor authentication enabled — even for accounts with “Full access” roles. Business Settings → Users → People → your profile will show the exact banner.
Only you/testers can connect; clients get a permissions errorStill at Standard Access — submit App Review for Advanced Access.
Publish fails with a 401/190 token errorThe stored Page token was invalidated (password change, app removed, permission revoked) — the user must reconnect.
Configured FACEBOOK_PAGE_ID not among the user’s PagesThe connecting account doesn’t admin that Page; clear the var or use the right account.

When the normal flow can’t see the Page: System User workaround

Section titled “When the normal flow can’t see the Page: System User workaround”

Symptom: you’ve done everything right — the connecting Facebook profile is an App role holder, has “Full access: Everything” on the Page in Business Manager, 2FA is on, Business Verification is Verified, and Graph API Explorer confirms the token carries pages_show_list/pages_manage_posts/pages_read_engagement — and yet /me/accounts still returns an empty list, so the connect callback fails with “No Facebook Pages found for this account.”

This happened connecting the Viite.ai Page (2026‑07‑02) and took a long diagnostic chain to confirm. It is a genuine, unresolved Meta platform limitation, not a misconfiguration: for Pages that live natively inside a Business Portfolio (never migrated from a classic personally‑owned Page), the legacy /me/accounts lookup — which personal‑OAuth Page connection has always relied on — does not reliably reflect Business‑Portfolio page‑role grants. Confirmed independently of our code by testing the same personal account directly against Graph API Explorer: both GET /me/accounts and Explorer’s own “Get Page Access Token” picker returned zero Pages. No amount of re‑clicking Business Settings fixes it.

Diagnostic order (don’t skip steps — each has its own distinct fix)

Section titled “Diagnostic order (don’t skip steps — each has its own distinct fix)”
  1. “Application inactive” on the consent screen → the app is in Development mode and the Facebook profile has no App role. Fix: App Dashboard → App roles → Roles → Add People, add that profile as Admin/Developer.
  2. Callback fails with “No Facebook Pages found” → confirm the profile is a Page admin. Business Settings → Accounts → Pages → [Page] → Page access → confirm “Full access” is assigned.
  3. Business Settings actions blocked with “You don’t have access to take this action” → the account needs 2FA enabled before Meta allows any Business Portfolio management action. Business Settings → Users → People → your profile shows a red “Two‑factor authentication required” banner when this is the cause.
  4. Still empty after all of the above → verify independently via Graph API Explorer: select the app, generate a User Token with the three Page permissions, run GET /me/accounts?fields=id,name,access_token. If this — Meta’s own official tool — also returns {"data": []}, you’ve hit this bug. Stop trying more Business Settings clicks; move to the System User workaround below.

The workaround: a System User Page Access Token

Section titled “The workaround: a System User Page Access Token”

A System User is Meta’s recommended mechanism for server‑side access to a Business‑Portfolio‑owned asset, and it bypasses /me/accounts entirely — the token is generated with the Page already scoped in, not resolved by a personal‑profile lookup.

  1. Business Settings → Users → System users → Add. Name it (e.g. <app>-publisher), role Admin.

  2. Assign the app role. Business Settings → Accounts → Apps → [your app]Assign access → select the System User → grant it access. (Without this step, “Generate Token” shows “No permissions available.”)

  3. Assign the Page as an asset. System users → [the system user]Assigned assets tab → assign the target Page with Manage Page permission. (This is separate from step 2 — both are required, and it’s easy to do only one.)

  4. Generate the token. System users → [the system user]Generate New Token → select your app → check pages_show_list, pages_manage_posts, pages_read_engagement → generate (no expiration by default).

  5. Paste it into the platform. Dashboard → Settings → Connected social accounts → Facebook Page → “Trouble connecting? Paste a token manually.” Paste the System User token from step 4 and submit — the server does the rest:

    • Calls GET /me/accounts with the pasted token to resolve the real, Page‑scoped access_token (a System User token authenticates as the system user itself, not the Page — this hop is required).
    • If the System User administers several Pages, the form shows a picker to disambiguate instead of guessing.
    • If /me/accounts comes back empty, it falls back to treating the pasted value as an already‑resolved Page token (covers a token copied straight from Graph API Explorer’s own Page picker).
    • Validates the resolved token against the Graph API and stores it on the connecting user’s owner‑scoped channel — exactly what the OAuth callback would have written, had /me/accounts worked for the personal account.

    Code: FacebookOAuthClient.resolveManualToken() (apps/api/publishing/oauth/FacebookOAuthClient.ts), wired through POST /api/connect/facebook/manual (apps/api/social/routes/socialOAuth.routes.ts).

  6. Verify. GET /api/connectors (owner‑scoped, configs masked) should now list a facebook channel. Publishing works exactly as normal from here — the publish worker doesn’t know or care whether the token came from the OAuth dance or was seeded manually.

This is a one‑time recovery per Page, not an ongoing process — System User tokens generated without an expiration don’t need refreshing. The dashboard’s Connected social accounts card also shows each connection’s expiry (a “Long‑lived” badge for tokens over a year out, a warning badge as a real expiry approaches) so this doesn’t need to be rediscovered by digging through logs next time.

  • Graph API version pinned by the platform: v25.0 (versions). Always pinned explicitly so a new Meta version can’t silently break publishing.
  • Code: apps/api/publishing/oauth/FacebookOAuthClient.ts, apps/api/publishing/platforms/FacebookPublisher.ts, wired in buildConnect / buildSocialPublishers.