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 app | You, once | Every client |
| Client setup | Click “Connect Facebook”, pick a Page | Create a developer app, configure login, paste secrets |
| App Review / Business Verification | You 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 shows | your brand (“Viite is requesting…”) | the client’s brand |
| Rate limits | Per‑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.
Prerequisites
Section titled “Prerequisites”- 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.
- Open the App Dashboard → Create app.
- Name the app and confirm a contact email.
- 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.
- Attach your Business portfolio when prompted (you may defer this for early local testing, but it’s required before App Review).
- 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.
-
In the app, open Facebook Login for Business → Configurations → Create configuration.
-
Set the access token type to User access token.
-
Select the asset type the app needs: Pages.
-
Add the permissions (next step) to the configuration.
-
Save — Meta returns a Configuration ID (
config_id). Copy it; it becomes theFACEBOOK_LOGIN_CONFIG_IDenv var. The connect flow then passesconfig_id(and omitsscope, per Meta’s recommendation). -
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/callbackIt must match the
FACEBOOK_OAUTH_REDIRECT_URIenv var byte‑for‑byte. For local testing addhttp://localhost:4000/...(or your API port) too.
Docs: Facebook Login for Business · Manual login flow
Step 3 — Request the Page permissions
Section titled “Step 3 — Request the Page permissions”Add these to the use case’s Permissions tab (and to the FLB configuration):
| Permission | Why | Default |
|---|---|---|
public_profile | Basic identity | auto‑granted |
pages_show_list | List the user’s Pages (/me/accounts → Page tokens) | review for Advanced Access |
pages_read_engagement | Read Page data (dependency of posting) | review for Advanced Access |
pages_manage_posts | Create/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
Step 4 — Get the App ID and App Secret
Section titled “Step 4 — Get the App ID and App Secret”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.
Access levels: what works when
Section titled “Access levels: what works when”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)
Step 5 — Set the platform env vars
Section titled “Step 5 — Set the platform env vars”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 var | Required | Notes |
|---|---|---|
FACEBOOK_CLIENT_ID | ✅ | The Meta App ID. Non‑secret. |
FACEBOOK_CLIENT_SECRET | ✅ | The Meta App Secret. 🔒 secret — never expose. |
FACEBOOK_OAUTH_REDIRECT_URI | ✅ | Byte‑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_KEY | ✅ | Shared with X/LinkedIn; encrypts stored tokens at rest. |
FACEBOOK_LOGIN_CONFIG_ID | optional | The Facebook Login for Business configuration id. Set it to pass config_id; leave empty for classic scope‑based login. Non‑secret. |
FACEBOOK_PAGE_ID | optional | Disambiguates 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:
- 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.comchannel they own. - Compose a post targeting that Facebook destination — via the unified content
form, or a
socialVeille‑style preset whose dispatch destination points atfacebook.com. - Approve + schedule the draft in the review queue.
- The
publishworker 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.
Multi-tenancy deep dive
Section titled “Multi-tenancy deep dive”Rate limits at scale
Section titled “Rate limits at scale”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.
When a tenant needs its own app
Section titled “When a tenant needs its own app”- 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.
Instagram & WhatsApp (future)
Section titled “Instagram & WhatsApp (future)”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.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
Connect redirect errors with redirect_uri mismatch | FACEBOOK_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 connect | The 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 dialog | The 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 error | Still at Standard Access — submit App Review for Advanced Access. |
| Publish fails with a 401/190 token error | The stored Page token was invalidated (password change, app removed, permission revoked) — the user must reconnect. |
Configured FACEBOOK_PAGE_ID not among the user’s Pages | The 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)”- “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.
- 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.
- 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.
- 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.
-
Business Settings → Users → System users → Add. Name it (e.g.
<app>-publisher), role Admin. -
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.”)
-
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.)
-
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). -
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/accountswith the pasted token to resolve the real, Page‑scopedaccess_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/accountscomes 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/accountsworked for the personal account.
Code:
FacebookOAuthClient.resolveManualToken()(apps/api/publishing/oauth/FacebookOAuthClient.ts), wired throughPOST /api/connect/facebook/manual(apps/api/social/routes/socialOAuth.routes.ts). - Calls
-
Verify.
GET /api/connectors(owner‑scoped, configs masked) should now list afacebookchannel. Publishing works exactly as normal from here — thepublishworker 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.
Reference
Section titled “Reference”- 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 inbuildConnect/buildSocialPublishers.
