Compose once. Publish everywhere.
A multi-platform social scheduler with live analytics, a unified inbox, and automation built in. One composer publishes to nine networks in parallel, and everything the dashboard can do is also available over a REST API, a CLI, and an MCP server for AI agents.
social0.app · Docs · API reference · MCP
142,000 lines of TypeScript · 423 tests · 49 migrations · 9 networks · live at social0.app, first commit February 2026
- Parallel publishing. Every selected account receives the post at the same time, and one platform failing never blocks the rest.
- Live publish progress. "Publish now" streams per-platform status over SSE until every target settles.
- One calendar. Scheduled, queued, draft and published posts across all connected accounts.
- Analytics from the source. Metrics are fetched live from each platform for posts published through Social0, never estimated.
- Unified inbox. Comments and DMs from connected accounts, with reply, like and hide in place.
- Teams and workspaces. Admin, member, community and analyst roles, so an analyst sees analytics and a community manager answers the inbox without gaining publish rights.
- Built for agents. One API key unlocks the same surface from
curl, thesocial0CLI, or any MCP client. - Encrypted at rest. Platform OAuth tokens are encrypted, never logged, and refreshed by a token-health cron.
| Platform | Connect | Publish | Analytics | Comments | DMs |
|---|---|---|---|---|---|
| X (Twitter) | OAuth 1.0a | Text, images, video, threads | ✅ | ✅ | ✅ |
| OAuth 2 | Profiles and company pages | ⏳ | ⏳ | n/a | |
| Meta OAuth | Images, video, carousels | ⏳ | ⏳ | ⏳ | |
| Meta OAuth | Pages only | ✅ | ✅ | n/a | |
| Threads | Meta OAuth | Text, images, video | ✅ | ✅ | n/a |
| TikTok | Login Kit (PKCE) | Video and photo posts | ✅ | n/a | ⏳ |
| YouTube | Google OAuth | Videos and Shorts | ✅ | ✅ | n/a |
| Pinterest OAuth | Pins with board picker | ✅ | n/a | n/a | |
| Bluesky | Handle + app password | Text, images, video | ✅ | ✅ | ✅ |
✅ live · ⏳ built, waiting on the platform's App Review · n/a no public API for it
Publishing works on every platform above. Read features are gated per platform in
live-platforms.ts, which is the single source of
truth, and a flag flips the moment access is approved. FEATURES.md has the full
product surface.
flowchart LR
SPA["SPA<br/>React + Vite<br/>Cloudflare Pages"] -->|"session cookies<br/>POST /api/rpc"| API
CLI["social0 CLI"] -->|"Bearer API key"| API
MCP["MCP server<br/>ChatGPT / Claude / Cursor"] -->|"/v1"| API
API["Fastify API<br/>auth, validation, enqueue"] --> DB[("Postgres<br/>Neon")]
API --> R2[("R2<br/>media")]
API --> REDIS[("Redis<br/>queues, limits")]
API -->|"HMAC enqueue"| QUEUE[["Cloudflare Queues"]]
CRON["cron-worker"] -->|"POST /api/cron/*"| API
API --> BG["background-worker<br/>scheduled posts, repost,<br/>autoplug, token health"]
BG --> QUEUE
QUEUE --> PW["publish-worker<br/>Hyperdrive + R2"]
PW --> PLATFORMS{{"Platform APIs"}}
PW -->|"finalize, emails, webhooks"| DB
The rule that shapes the whole system: HTTP handlers never wait on a platform API. A publish
request validates, writes publish_jobs and post_publications, fans out one job per platform and
returns immediately. Uploads that can take minutes (TikTok, YouTube, Meta video) run on the
Cloudflare publish worker, which finalizes the post, sends failure emails and delivers user
webhooks. Analytics and inbox reads are the deliberate exception: they call platform APIs during the
request, under timeouts and a live-read budget.
A scheduler looks simple from the outside. These are the parts that took the real work.
Nine APIs that agree on nothing. Auth alone spans OAuth 1.0a, OAuth 2, PKCE and app passwords.
X needs chunked upload for video. Meta returns a media id that is not a URL, so the permalink takes a
second call. TikTok hands back a publish id that only resolves to a public video minutes later, so
the URL is backfilled from video.list by publish time. Each network gets
its own module; nothing leaks into shared code.
Finalization is a race. Platform jobs finish concurrently, and the last one to land has to decide whether the post as a whole succeeded. Failure emails and user webhooks are each claimed exactly once through a metadata claim on the post, so nine parallel jobs cannot send nine emails.
Dead tokens are not errors. A 401, or X error code 89, means the user must reconnect, not that
the request should be retried. Those map to a scope_missing status carrying the reason, so every
surface shows a reconnect prompt instead of a retry loop. LinkedIn rotates refresh tokens, so a
rotated token is persisted whenever one appears.
Live reads need a budget. Analytics and inbox call platform APIs inside the request. Responses
carry independent coverage flags: sampled means the publication page was capped, partial means
the live budget ran out. Folding them together would quietly misreport.
Workers end the moment you return. On Cloudflare, a fire-and-forget email or webhook never leaves, because the isolate is torn down with the response. Anything that must go out is awaited before the handler returns.
An agent with an API key has the same abilities as the dashboard. Point any MCP client at
https://mcp.social0.app/mcp, or run @social0/mcp over stdio:
"Draft a launch post, schedule it for Tuesday 9am on X and LinkedIn,
then show me last week's engagement."
That one sentence is schedule_content, then get_analytics. Agents also get publish_now,
upload_media, get_post_analytics, list_inbox_comments, reply_to_comment, list_inbox_dms
and reply_to_dm.
The same surface, without an agent:
# REST
curl -H "Authorization: Bearer $SOCIAL0_API_KEY" https://api.social0.app/v1/accounts
# CLI
npx social0 login
npx social0 publish --content "Launching today!" --platform twitter linkedin
npx social0 analyticsKeys come from Dashboard → API keys.
| Path | What lives there |
|---|---|
frontend/ |
The SPA: marketing, auth, onboarding, dashboard, composer, analytics, inbox |
backend/server/ |
Fastify API: auth, validation, RPC, billing, OAuth connect, /v1 |
backend/server/src/lib/publish-platforms/ |
One file per network, where a new platform goes |
backend/background-worker/ |
Cron consumers: scheduled publish, repost, autoplug, token health |
backend/shared/ |
Queue names, job payloads, the Cloudflare enqueue client |
cloudflare/publish-worker/ |
Edge publishing with Hyperdrive and R2 |
cloudflare/cron-worker/ |
Scheduled triggers that call the API's cron routes |
cloudflare/mcp-worker/ |
Hosted MCP endpoint with OAuth |
social0-cli/ |
The public social0 CLI |
social0-mcp/ |
The public @social0/mcp server |
The SPA and the backend are separate packages. Cloudflare Pages builds frontend/ alone, so
nothing under backend/ may be imported into it.
You will need: Node 20+, a Postgres database (Neon works well), a Redis instance (Upstash free tier is enough), and Cloudflare R2 for media. Platform OAuth apps are only needed for the networks you actually want to connect.
git clone https://github.com/Abhishek-B-R/social0-oss.git
cd social0-oss
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
npm run install:all # installs frontend and backend separately, as production does
npm run dev # API :3001, background worker, SPA :5173Vite proxies /api and /v1 to the API, so leave VITE_API_URL empty for local development.
| Command | What it does |
|---|---|
npm run dev |
SPA, API and background worker together |
npm run dev:frontend / dev:server / dev:worker |
One piece at a time |
npm run build |
Build backend, then frontend |
npm run typecheck |
TypeScript across both trees |
npm run lint |
ESLint across both trees |
npm run db:migrate |
Apply migrations |
Scheduled posts only publish if something calls /api/cron/* on a schedule. In production that is
cloudflare/cron-worker.
| Piece | Runs on |
|---|---|
| SPA | Cloudflare Pages, building frontend/ |
| API and background worker | A VM under PM2 |
| Publish, cron and MCP workers | Cloudflare Workers, deployed with Wrangler |
| Database | Postgres (Neon), reached from the edge through Hyperdrive |
| Media | Cloudflare R2 |
| Queues and rate limits | Cloudflare Queues and Redis |
Publishing defaults to the Cloudflare worker (PUBLISH_DISPATCH=cloudflare) and falls back to
BullMQ on the API host. X and TikTok deliberately publish from the API process, where chunked video
upload and 64-bit permalinks are handled.
| Document | Covers |
|---|---|
FEATURES.md |
The complete product surface, plan by plan |
CONTRIBUTING.md |
Setup and where to make each kind of change |
CLAUDE.md |
Architecture guide, also written for AI coding agents |
backend/README.md |
API and worker internals |
frontend/README.md |
SPA structure, routing and theming |
cloudflare/publish-worker/README.md |
Edge publishing and its bindings |
docs/ |
Platform permissions and analytics scopes |
Issues and pull requests are welcome. CONTRIBUTING.md explains the setup and
points you at the right directory for each kind of change. Adding a network, for instance, is a new
file under backend/server/src/lib/publish-platforms/. Please run npm run typecheck and
npm run lint before opening a PR.
One thing to know about this repository: it is published from the repository that runs social0.app, and every release overwrites it. Accepted pull requests are applied upstream and arrive here on the next publish, so your PR may be closed as applied rather than merged, and commits pushed straight to this repository do not survive.
AGPL-3.0. The social0-cli and social0-mcp packages are MIT, so anyone can build
clients against the API freely.
