Social overview
Publish, schedule, monitor, and report on organic social across nine networks from one tenant-isolated workspace — the composer, calendar, inbox, listening, analytics, and compliance surfaces built on /v1/social.
Pact Social
Pact Social is the organic social-media surface of the platform: connect a
workspace's LinkedIn, Meta, X, and other network accounts once, then draft,
schedule, publish, monitor replies, listen for brand mentions, and report on
performance — all inside the same multi-tenant, RBAC-enforced app that owns your
CRM and marketing data. Every route lives under the /v1/social prefix and
derives tenant_id from the auth context, so one workspace never sees another's
accounts, posts, or engagements.
This surface is live
The composer, calendar, inbox, listening, analytics, and compliance flows are
backed by real, tenant-isolated endpoints and Postgres tables
(tenant_social_accounts, social_posts, social_engagements,
social_mentions, tenant_listening_queries, social_listening_matches,
social_scheduled_reports, and more). The one exception is the content
Library, which currently persists to the browser — see below.
The surfaces
Supported networks
The provider registry (core/integrations/social/registry.py) ships factories
for nine networks:
| Network | Slug(s) | Notes |
|---|---|---|
linkedin | Personal feed, company page, and share; full OAuth + client. | |
| Meta | facebook, instagram, threads | Page / IG / Threads posting via the Meta Graph client. |
| X (Twitter) | twitter | v2 API over OAuth 2.0 PKCE; requires a paid X Basic tier (~$100/mo). |
| Bluesky | bluesky | AT Protocol. |
| Mastodon | mastodon | Instance-scoped. |
| TikTok | tiktok | Video posting. |
| YouTube | youtube | Video + description. |
Each provider implements the same SocialProvider contract
(core/integrations/social/base.py): authorize_url, complete_oauth,
refresh_token, post, list_engagements, list_mentions, reply,
analytics, and delete. A network only becomes connectable once an admin
supplies its app credentials through the BYOK credentials wizard — the provider
factory resolves them per-tenant at request time, so nothing is hardcoded.
How posting works
POST /v1/social/posts creates a post. Its status moves through the states in
core/integrations/social/store.py:
draft: saved, not sent.scheduled: has ascheduled_fortime. The post shows on the calendar and publishes itself when that time comes.publishing: claimed by a publisher and on its way to the network. A post in this state can't be claimed again, so the job and a person pressing Publish now can't both send it.published: sent.provider_post_idandprovider_url(the link to the live post) are kept on the post.failed: not sent.failure_reasongives the network's reason in plain words. Try again publishes the post now.
publish_now: true publishes the post as soon as it's created. For a draft, a
scheduled post, or a failed post, use POST /v1/social/posts/{public_id}/publish.
Scheduled publishing
The worker tick social.publish_due runs every minute. It finds scheduled posts
that are due and publishes each one through the provider layer in
core/integrations/social/publish.py. Before contacting the network, it commits
its claim on the post. The rules below favor a late post over a duplicate
post, because a duplicate is public and permanent:
- Retries. Pact retries only when the network definitely did not receive
the post: a rate limit (
429) or a connection that never opened. It makes up to 3 attempts in total, waiting 2 and then 10 minutes, or as long as the network asks. While it waits, the post shows Retrying with the attempt number, the reason, and the time of the next try. - No automatic resend when the outcome is unknown. Pact won't resend a post
if the network timed out after receiving it, or if Pact stopped mid-send and
the claim is more than 10 minutes old. The post moves to
failedwith a reason telling you to check the network before you try again. - No late publishing. Pact won't publish a post on its own if it's more than
24 hours past its time, for example after an outage. The post moves to
failedwith a reason that says how late it is. - Paused workspaces. Posts in a suspended workspace, or one pending deletion, stay scheduled and aren't sent.
- Sample data. Seeded sample posts are never sent to a network.
Scheduled analytics reports are a separate mechanism; see Social analytics.
Roles and audit
Any workspace member can create posts; connecting or disconnecting an account is
restricted to admin/owner (the same policy as the credential store). Every
meaningful action writes an audit event — social.account.connected,
social.post.published, social.engagement.received,
social.listening.alert.fired, and peers — and secrets never enter those
payloads: only ids, kinds, names, and the connected account's display name.