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.

1
Your app → PostFuze
Request an authorize URL with POST /social-networks/{platform}/auth-url
Send your API key and an optional redirect_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.
2
Your app → Customer
Redirect the customer to auth_url
They review the requested scopes on the platform and approve (or deny).
3
Customer → Platform
The customer authorizes on the platform
The platform issues an authorization code against your BYOK (or the shared) OAuth app.
4
Platform → PostFuze → Customer
The platform redirects to PostFuze's callback, which bounces back to your redirect_uri
The platform calls /connect/callback. PostFuze exchanges the code, encrypts and stores the tokens, then redirects the browser to your redirect_uri with the callback parameters below.
5
Your app → PostFuze (only when pending)
If status=pending, list the pages and finalize
GET /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

Every network can be connected with your own OAuth app (BYOK) today — register it with POST /social-networks for a white-label consent screen and your own rate limits. Managed connect— PostFuze's shared OAuth apps, so your customers connect in one click with no developer-app setup — is rolling out per platform as each app clears the provider's review; networks where it isn't live yet show as one-click coming soon. When a managed app is live for a platform, the connect flow uses it automatically unless you've saved your own. See Bring 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
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"
  }'
200 OK
{ "auth_url": "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=…&state=…" }
FieldTypeDescription
redirect_uristring (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_idstringOptional. 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.
scopesstring[]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:

OutcomeAppended query parametersMeaning
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:

Your redirect handler — pseudo-code
// 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

PostFuze only redirects to an allow-listed origin: your 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 — list pending pages
curl https://api.postfuze.com/api/v1/social-accounts/pending/SESSION_TOKEN \
  -H "Authorization: Bearer sk_live_…"
200 OK
{
  "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 — finalize
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…"] }'
200 OK — finalize
{
  "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 — connect Bluesky
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"
  }'
201 Created
{
  "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

Subscribe to 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.connected and publish-result events.