Connecting accounts
Your API key authenticates you; to publish on behalf of a customer, each of them connects their own social account through PostFuze's hosted OAuth flow. Your backend asks for an authorize URL, sends the customer to the platform, and PostFuze exchanges the code and stores the encrypted tokens — your key never leaves your server and you never touch the platform tokens. This guide documents the full round trip and, in particular, the callback redirect contract: what PostFuze appends to the redirect_uri you control, so you can finish the flow.
POST /social-networks/{platform}/auth-urlredirect_uri and tenant_id. PostFuze returns an auth_url with a signed, encrypted state that carries your redirect_uri, the tenant_id, and (for PKCE platforms) the code verifier.auth_urlredirect_uri/connect/callback. PostFuze exchanges the code, encrypts and stores the tokens, then redirects the browser to your redirect_uri with the callback parameters below.status=pending, list the pages and finalizeGET /social-accounts/pending/{token} returns the choices; POST .../finalize with the selected IDs connects them and returns the new account_ids.Two ways to connect: managed one-click or your own app
Step 1 — Request an authorize URL
Connecting requires an OAuth app for the platform — either your own BYOK app (register it with POST /social-networks; see Social networks) or PostFuze's shared connect app where enabled. Then ask for an authorize URL:
curl https://api.postfuze.com/api/v1/social-networks/x/auth-url \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"redirect_uri": "https://yourapp.com/connected",
"tenant_id": "user_42"
}'{ "auth_url": "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=…&state=…" }| Field | Type | Description |
|---|---|---|
redirect_uri | string (URL) | Optional. Where PostFuze bounces the customer's browser after the callback. Must pass the allow-list (see below). If omitted or rejected, PostFuze renders its own hosted result page instead of redirecting. |
tenant_id | string | Optional. Your own identifier for the end customer. It is sealed into the state and stamped onto the connected account, so you can attribute the account to one of your users. |
scopes | string[] | Optional. Override the default scopes requested for this authorization. |
The returned state is an encrypted, signed token — never craft or modify it yourself. It carries your redirect_uri and tenant_id through the round trip so the callback can honor them after the platform redirects back.
Step 2 — Send the customer to the authorize URL
Redirect the customer's browser to auth_url. They approve the requested scopes on the platform, and the platform issues an authorization code. Point the platform's redirect URI at PostFuze's callback, not at your own app — configure https://api.postfuze.com/api/v1/connect/callback as the redirect URI registered on your OAuth app and saved on the social network. PostFuze owns the code exchange; your redirect_uri is where PostFuze sends the customer afterwards.
Step 3 — The callback redirect contract
After the platform calls /connect/callback, PostFuze exchanges the code, encrypts and stores the tokens, and redirects the browser to your redirect_uri with query parameters describing the outcome. There are two outcomes:
| Outcome | Appended query parameters | Meaning |
|---|---|---|
| Single account | ?status=connected&account_id=<id> | The authorization yielded exactly one account; it is connected and ready. account_id is the social account ID you target when publishing. |
| Multiple accounts | ?status=pending&session=<token> | The authorization exposed several pages/accounts (e.g. Facebook Pages or Google Business locations). Nothing is connected yet — finish with the pending session (step 4). |
PostFuze sets these parameters on your URL (any existing query string is preserved). Read status first, then branch on account_id or session:
// GET https://yourapp.com/connected?status=connected&account_id=a1b2c3d4-…
const url = new URL(request.url);
const status = url.searchParams.get('status');
if (status === 'connected') {
const accountId = url.searchParams.get('account_id'); // persist + use in accounts[] when publishing
} else if (status === 'pending') {
const session = url.searchParams.get('session'); // run the page-selection step (step 4)
}redirect_uri must be on the allow-list
redirect_uri must be an absolute http(s) URL whose origin exactly matches PostFuze's configured app origin (APP_URL). A redirect_uri that fails the check — wrong origin, relative URL, or non-http scheme — is dropped to prevent open redirects, and PostFuze renders its own hosted result page instead of bouncing the browser. If you need to land customers on your own domain, have PostFuze configure your origin in the allow-list; otherwise omit redirect_uri and rely on the hosted page plus webhooks (account.connected) to learn when an account is ready.Step 4 — Finish a pending (multi-account) connection
When the callback returns status=pending&session=<token>, the authorization exposed more than one page/account and you choose which to connect. Both calls are authenticated with your API key. First list the options:
curl https://api.postfuze.com/api/v1/social-accounts/pending/SESSION_TOKEN \
-H "Authorization: Bearer sk_live_…"{
"network": "facebook",
"availablePages": [
{ "id": "1784523…", "type": "page", "name": "Acme Co", "username": "acme", "profilePictureUrl": "https://…" },
{ "id": "1990881…", "type": "page", "name": "Acme Support", "username": null, "profilePictureUrl": null }
]
}Then finalize with one or more of the id values. Each selected page becomes its own social account:
curl https://api.postfuze.com/api/v1/social-accounts/pending/SESSION_TOKEN/finalize \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{ "selectedPageIds": ["1784523…"] }'{
"connectedAccounts": [
{
"id": "a1b2c3d4-…",
"nickname": "acme",
"username": "acme",
"network": "facebook",
"accountType": "page"
}
]
}The pending session is short-lived and single-use: it is consumed on finalize and expires on its own, so list and finalize promptly. The id in connectedAccounts is the social account ID you target when publishing.
Bluesky: no OAuth round trip
Bluesky does not use OAuth — there is no authorize URL and no callback redirect. Instead the customer supplies a handle (or email) and an app password, and you connect the account in a single API-key-authenticated REST call. PostFuze validates the credentials against the PDS, reads the account identity, and stores the app password as the (non-expiring) refresh credential.
curl https://api.postfuze.com/api/v1/social-accounts/bluesky/connect \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"identifier": "acme.bsky.social",
"app_password": "xxxx-xxxx-xxxx-xxxx"
}'{
"id": "a1b2c3d4-…",
"network": "bluesky",
"username": "acme.bsky.social",
"account_type": "profile",
"status": "active"
}Generate the app password in Bluesky's settings — never the account password. The response's id is the social account ID you publish to. See Bluesky for details.
Confirm with webhooks instead of polling
account.connected (and account.disconnected / account.token_expired) so your backend learns when an account becomes ready — useful when you rely on the hosted result page rather than your own redirect_uri. See Webhooks.Next steps
- Social networks — register BYOK OAuth app credentials per platform.
- Social accounts — list, inspect and disconnect connected accounts.
- Quickstart — connect an account and publish your first post end to end.
- Webhooks — receive
account.connectedand publish-result events.