Backend integration
This is the recommended way to integrate PostFuze into a multi-user product: register a BYOK OAuth app per platform, drive the connect flow from your backend, persist the account IDs PostFuze returns against your own users, then publish and stay in sync via webhooks. Your PostFuze API key lives only on your server.
Architecture at a glance
- Register OAuth apps once per platform with
POST /social-networks(BYOK). - Connect accounts per end-user: get an auth URL, redirect, and store the returned social account IDs against your
tenant_id. - Create posts targeting those account IDs.
- Sync state via webhooks (preferred) or polling.
Throughout, pass your own user identifier as tenant_id so PostFuze tags every connected account and import job with it. That lets you filter accounts per user later without keeping a separate mapping in sync — though storing the IDs yourself (below) is still recommended for speed and joins.
POST /posts with your API keyStep 1 — Register a BYOK app
PostFuze uses your platform developer-app credentials. Register them once per platform; the upsert key is (org, platform), so re-posting updates them in place. The client_secret is encrypted at rest and never returned.
curl https://api.postfuze.com/api/v1/social-networks \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"platform": "linkedin",
"display_name": "Acme LinkedIn App",
"client_id": "YOUR_LINKEDIN_CLIENT_ID",
"client_secret": "YOUR_LINKEDIN_CLIENT_SECRET",
"redirect_uri": "https://api.postfuze.com/api/v1/connect/callback",
"scopes": ["r_basicprofile", "w_member_social"]
}'Set the platform's OAuth redirect URI to PostFuze's callback: https://api.postfuze.com/api/v1/connect/callback. PostFuze handles the code exchange and token storage for you. See Social networks for the full field list.
Step 2 — Connect an account and store the ID
When a user wants to connect, request an auth URL and include your own user identifier as tenant_id plus the redirect_uri you want PostFuze to bounce back to.
curl https://api.postfuze.com/api/v1/social-networks/linkedin/auth-url \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"redirect_uri": "https://yourapp.com/integrations/postfuze/return",
"tenant_id": "user_42"
}'{ "auth_url": "https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=…&state=…" }Redirect the user to auth_url. After they approve, PostFuze's callback exchanges the code and bounces back to your redirect_uri with one of two outcomes:
| Query params | Meaning | What to do |
|---|---|---|
?status=connected&account_id=… | A single account was connected immediately. | Store account_id against the user. |
?status=pending&session=… | Several pages/accounts are available (e.g. Facebook Pages or Google Business locations). | Run the page-selection flow below. |
For the pending case, fetch the available pages and finalize the user's selection:
# List available pages for the pending session
curl https://api.postfuze.com/api/v1/social-accounts/pending/SESSION_TOKEN \
-H "Authorization: Bearer sk_live_…"
# Finalize one or more selections
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…", "1784524…"] }'{
"network": "facebook",
"availablePages": [
{ "id": "1784523…", "type": "page", "name": "Acme Co", "username": "acme", "profilePictureUrl": "https://…" },
{ "id": "1784524…", "type": "page", "name": "Acme EU", "username": "acme_eu", "profilePictureUrl": "https://…" }
]
}{
"connectedAccounts": [
{ "id": "a1b2c3d4-…", "nickname": "acme", "username": "acme", "network": "facebook", "accountType": "page" }
]
}Persist the social account IDs (account_id / connectedAccounts[].id) against your user. These are the IDs you pass in accounts[]when publishing. You can re-list a user's accounts any time, filtered by your tenant identifier:
curl "https://api.postfuze.com/api/v1/social-accounts?tenant_id=user_42" \
-H "Authorization: Bearer sk_live_…"{
"data": [
{
"id": "a1b2c3d4-…",
"platform": "facebook",
"tenant_id": "user_42",
"social_network_id": "f1e2d3c4-…",
"network_unique_id": "1784523…",
"username": "acme",
"display_name": "Acme Co",
"profile_image_url": "https://…",
"account_type": "page",
"status": "active",
"scopes": ["pages_manage_posts", "pages_read_engagement"],
"token_expires_at": "2026-08-08T10:00:00.000Z",
"last_synced_at": "2026-06-09T10:00:00.000Z",
"created_at": "2026-06-09T10:00:00.000Z",
"updated_at": "2026-06-09T10:00:00.000Z"
}
]
}Step 3 — Create posts
Target the stored account IDs. A post fans out to one independent target per account. Use an Idempotency-Key header (or an external_reffield) so retries don't create duplicates — replaying a seen key returns the original post with 200. Per-platform overrides go in config, keyed by platform.
curl https://api.postfuze.com/api/v1/posts \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: campaign_91:launch" \
-d '{
"containers": [
{ "content": "Big news today 🚀", "media": [{ "id": "media_abc", "alt_text": "Launch banner" }] },
{ "content": "More details in the thread 👇" }
],
"accounts": ["a1b2c3d4-…", "b2c3d4e5-…"],
"scheduled_at": "2026-06-10T15:00:00.000Z",
"config": { "youtube": { "isShort": true } }
}'Container 0 is the main post (role: "main"), container 1 becomes the first_comment, and any further containers are thread replies. See First comments & threads and Posts for details.
Step 4 — Stay in sync
Webhooks (preferred)
Register an endpoint and subscribe to events instead of polling. PostFuze returns a signing_secret once, on creation — store it and verify the signature on every delivery.
curl https://api.postfuze.com/api/v1/webhooks \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/hooks/postfuze",
"events": ["post.published", "post.error", "account.token_expired", "import.completed", "import.failed"],
"description": "Production sync"
}'{
"id": "wh_…",
"url": "https://yourapp.com/hooks/postfuze",
"events": ["post.published", "post.error", "account.token_expired", "import.completed", "import.failed"],
"is_active": true,
"description": "Production sync",
"signing_secret": "whsec_…",
"created_at": "2026-06-09T10:10:00.000Z",
"updated_at": "2026-06-09T10:10:00.000Z"
}On a post.published or post.error event, update the matching row in your database. Treat account.token_expired as a signal to prompt the user to reconnect. See Webhooks for the full event catalog, payload shapes and signature verification.
Polling (fallback)
If you cannot receive webhooks, poll GET /posts/{id} until every target leaves the queued / pending state. The post-level status rolls up the per-target statuses, and each target carries its own platform_post_id, platform_post_url and any error_code / error_message. Back off exponentially and stop once all targets are terminal (published or failed).
Suggested database schema
Mirror just enough of PostFuze's state to drive your UI and joins; PostFuze remains the source of truth. A minimal Postgres schema:
-- One row per platform OAuth app you registered (BYOK). Mirrors social_networks.
create table postfuze_networks (
id uuid primary key, -- PostFuze social_networks.id
platform text not null, -- 'x' | 'linkedin' | 'instagram' | …
display_name text,
created_at timestamptz not null default now(),
unique (platform)
);
-- One row per connected account, linked to YOUR user.
create table postfuze_accounts (
id uuid primary key, -- PostFuze social_accounts.id (use in accounts[])
user_id uuid not null references users (id),
tenant_id text not null, -- the tenant_id you sent to /auth-url
platform text not null,
network_unique_id text not null, -- platform's own id for the account/page
username text,
display_name text,
account_type text, -- 'account' | 'page' | …
status text not null, -- 'active' | 'disabled' | …
token_expires_at timestamptz,
last_synced_at timestamptz,
created_at timestamptz not null default now(),
unique (platform, network_unique_id)
);
create index on postfuze_accounts (user_id);
-- One row per post you create, with a rolled-up status updated from webhooks.
create table postfuze_posts (
id uuid primary key, -- PostFuze posts.id
user_id uuid not null references users (id),
external_ref text, -- your idempotency key
status text not null, -- 'draft'|'scheduled'|'queued'|'published'|'failed'|'canceled'
scheduled_at timestamptz,
published_at timestamptz,
created_at timestamptz not null default now(),
unique (external_ref)
);
-- One row per (post, account) target — the unit that succeeds or fails.
create table postfuze_post_targets (
id uuid primary key, -- PostFuze post_targets.id
post_id uuid not null references postfuze_posts (id),
account_id uuid not null references postfuze_accounts (id),
platform text not null,
status text not null, -- 'queued'|'pending'|'published'|'failed'
platform_post_id text,
platform_post_url text,
error_code text,
error_message text,
published_at timestamptz
);
create index on postfuze_post_targets (post_id);Use your external_ref (the idempotency key) as the natural join between your domain and PostFuze's posts, and network_unique_id when reconciling accounts after a reconnect. Keep postfuze_post_targets.status authoritative from webhook events; the post-level status is a convenience rollup.
Operational notes
- One key, server-side. Hold a single org-level
sk_live_…key on your backend. Do not ship keys to clients. See Authentication. - Idempotency everywhere. Always send an
Idempotency-KeyonPOST /postsso retried requests are safe. - Handle token expiry. When you receive
account.token_expired, mark the account stale and re-run the connect flow for that user. - Backfill on connect. To pull a user's existing posts, queue an import with
POST /social-accounts/{id}/importsand react to theimport.completedwebhook. See Imports.
Next steps
- Quickstart — the five-minute end-to-end walkthrough.
- Posts — full request reference, scheduling and per-platform config.
- Webhooks — event catalog, payloads and signature verification.
- Social accounts — list, inspect, metrics and health.