User Manual · 03 · Connecting Platforms
WholeReach — Automated Marketing System

Connecting Social Platforms

Every network the hub can post to, what each one costs, what it needs to connect, and the exact steps to wire it up.

Just want the steps?Go straight to Playbook 1 — Connect a social account for the exact click-by-click for each network. This page explains the landscape and what each connection needs.

Platform status & cost at a glance

on = connectedgate = needs your login / review
PlatformStatusWhat connecting needsCost to post
BlueskyconnectedAn app password — no developer app.Free
X / Twitter (@springnet)connectedA developer app at console.x.com (OAuth).Paid — ~1.5¢/post, ~20¢ with a link
Mastodonapp readyConnect the account in the hub UI.Free
LinkedIngatedDeveloper app + app review.Free
Facebook PagesconnectedMeta developer app + Page access tokens. What it took →Free
Instagramconnected idleSame Meta app. Connected — but cannot receive text posts. Why →Free
Threads, Pinterest, TikTok, YouTube, Reddit…availableEach: a developer app; some need review.Mostly free
The real gate is never "can it post?"Posting is a solved problem. The one-time cost for each platform is creating the developer app and, for Meta/LinkedIn, passing their app review — a multi-day approval. Start those early.

How a connection actually works

Two patterns cover every platform:

1 · App-password platforms (easiest) — Bluesky, Mastodon

You generate a password in the account settings and paste it in. No developer app, no review.

2 · OAuth platforms — X, LinkedIn, Meta, most others

You create a developer app on the platform, tell it our callback address, and paste the app's keys into the hub. Clicking "connect" then does a secure handshake that stores an access token.

The callback address is always the same shape:

https://postiz.wholetech.com/integrations/social/<platform>

e.g. …/x, …/linkedin, …/facebook. Whitelist it in the platform's app settings.

Where the keys go (and how they stay safe)

Platform keys never travel through chat. They are installed by short prompts you run yourself in your own terminal:

# Meta (Facebook + Instagram)
ssh -t root@64.227.29.192 /opt/postiz/set-meta-keys.sh

# LinkedIn
ssh -t root@64.227.29.192 /opt/postiz/set-linkedin-keys.sh

Each script prompts for the keys, writes them to a locked-down file (/opt/postiz/social.env, readable only by root), and restarts the hub. See Security for the full policy.

A word on X billing

X is the one paid channelX closed its free posting tier in early 2026. Posting costs roughly 1.5¢ for text and ~20¢ when a post includes a link. The system caps X at a few posts per day so a small credit lasts months; every other connected network is free. See Cost control.

Facebook — what it actually took

Live since July 2026Two Facebook Pages publish automatically through the hub: Austinspring.com and AustinHangout. Test posts confirmed on both. Facebook was by far the hardest platform to connect, and every hour of that was spent on one of the five traps below — none of which produce an error message that tells you what is wrong.

The limit to understand before anything else

Pages can be automated. Personal profiles cannot.Meta provides no API for posting to a personal profile — not to us, not to Buffer, not to anyone. It was removed years ago and is not coming back. If an audience lives on a personal profile, no tool can automate to it; the only routes are posting by hand, or building a Page and moving the audience there over time. Worth knowing up front, because the follower counts on a new Page will look discouraging next to a long-established profile, and that gap is a starting point, not a failure.

The five traps, in the order you will hit them

What you seeWhat it actually meansFix
“Invalid Scopes: pages_manage_posts”The permission exists but was never added to the app's Pages use case. Requesting it in the token dialog is not enough.Meta app console → Use cases → Pages → add each permission explicitly.
“The domain of this URL isn't included in the app's domains”The App Domains field is empty.Add postiz.wholetech.com — and click the dropdown suggestion so it becomes a chip. Typed text that never became a chip does not save.
“Could not add provider” / 409 “Page not found in your accounts”The app secret was truncated on copy. This was the big one — it cost hours. A short secret fails silently at the OAuth layer and surfaces as an unrelated-looking error.The secret is 32 hex characters. Count them before using it. If a token exchange returns “Error validating client secret”, that is the tell.
Authorization fails for a second person or accountThe app is in Development mode, where only people with an app role can grant permissions.Add them under App roles → Roles as Tester and have them accept the invite — or take the app Live.
Everything is correct and the connect button still failsPostiz's own Facebook OAuth flow is buggy.Bypass it — see below. Do not keep re-clicking; the fault is not on your side.

Prove it before you debug it

The single most useful diagnostic is the Graph API Explorer at developers.facebook.com/tools/explorer. Generate a user token there and call /me/accounts. If your Pages come back with CREATE_CONTENT and MANAGE in their tasks, then permissions are fine and the problem is somewhere in the app wiring — which rules out most of the guesswork in one step. That is how this connection was finally diagnosed: the Pages were provably reachable while the hub still refused to add them, which pointed straight at the credential rather than the permissions.

The bypass, and why the tokens never expire

Because the hub's connect button was the broken link, the Page connections were written directly into its database instead. The tokens went through a three-step upgrade first:

short-lived user token   (1 hour, from Graph API Explorer)
   ↓  exchange: grant_type=fb_exchange_token
long-lived user token    (~60 days)
   ↓  request /me/accounts with it
Page access tokens        (never expire)
Why this matters operationallyA Page token derived from a long-lived user token does not expire — debug_token reports expires_at: 0. So Facebook publishing needs no reconnection cycle and no renewal cron. It keeps working until someone revokes it, changes the Page's admins, or resets the app secret. Get this step right once and the channel is done.

The two scripts that did it are kept for reuse when another account or Page is added: one inserts the Page rows, the other performs the token exchange and upgrades the stored tokens in place.

Instagram — connected, and why it is still idle

Connected July 2026Two Instagram Professional accounts publish-ready through the hub: @springnet (1,317 followers, 126 posts) and @paulwalhus. Both carry never-expiring tokens. After the Facebook groundwork this took under an hour.
But nothing is being sent to it — and that is not a bugInstagram accepts no text-only posts. Every post through the API must carry an image or a video. That is a platform rule, not a limitation of this system. The content engine produces text, so Instagram is deliberately left out of the channel list — adding it would fail on every single post. It stays connected and idle until there is an image pipeline.

What connecting it actually required

Less than expected. The account was already a Professional account, so no switch was needed. The genuinely missing piece was on the app side: in the Meta console, under the Instagram use case, choose API setup with Facebook login (not the Instagram-login variant) and add the required content permissions — instagram_basic, instagram_content_publishing, pages_read_engagement, business_management, pages_show_list. Then re-authorize and the Instagram accounts appear.

The trap that wasted the most time — a false negativeReading a Page's linked Instagram account requires instagram_basic. Without it, the API silently omits the field rather than returning an error — so a Page that is perfectly linked reads as “no Instagram account.” Two separate checks pointed the wrong way because of this. Never conclude a Page has no Instagram account from a token that lacks instagram_basic. Fix the scope first, then look.

Which Page an Instagram account hangs off

An Instagram Professional account attaches to one Facebook Page, and it is easy to be wrong about which. Here the main account, @springnet, turned out to be linked to AustinHangout rather than Austinspring.com, which is the opposite of what everyone assumed. Confirm it rather than trusting memory — ask the API for instagram_business_account on each Page and read what comes back. It takes seconds and settles the question permanently.

Adding another account later

A reusable script lives on the hub at /opt/postiz/connect-instagram.py. Give it a user token and it upgrades to never-expiring Page tokens, then inserts one row per linked Instagram account. It is idempotent, so running it twice is safe.

Handing over a token safelyAccess tokens should never be pasted into a chat or an email. The method that works: copy the token, then pipe it straight from the clipboard into the script over SSH — it is never displayed, never typed, and never stored in a transcript.
powershell -NoProfile -Command "Get-Clipboard -Raw" | \
  ssh root@64.227.29.192 'python3 /opt/postiz/connect-instagram.py'