How to build a social media SaaS product: architecture, APIs, and multi-tenant publishing

The technical architecture behind social media SaaS products — multi-tenant data models, OAuth delegation, job queues for scheduled publishing, webhook-driven delivery confirmation, usage metering, and how a publishing API replaces eight platform integrations.

How to build a social media SaaS product: architecture, APIs, and multi-tenant publishing

Every social media SaaS product solves the same five problems

The tools look different — a scheduling app, an agency dashboard, an AI content generator with built-in publishing, a brand monitoring platform with a “publish response” button. But underneath, they all need the same architecture:

  1. Multi-tenancy. Customers share the platform but their data, accounts, and content are isolated.
  2. OAuth delegation. Each customer connects their own social accounts. You do not hold their passwords.
  3. Job scheduling. Content publishes at specific times, not just when the user clicks “post.”
  4. Delivery confirmation. You need to know what published, what failed, and why — per platform, per customer.
  5. Usage tracking. You are billing customers, so you need to meter what they use.

This article walks through each layer. The goal is a production architecture, not a prototype — the kind of system that handles hundreds of customers with thousands of connected accounts and does not fall apart when a single platform changes its API.


The data model: tenants, accounts, and content

Before writing any publishing code, you need a data model that enforces isolation. A social media SaaS has three core entities:

At the top level sits your SaaS platform. Inside it, each tenant is a self-contained unit. Tenant A might have Instagram, X, and LinkedIn connected, with their own posts, drafts, and queue. Tenant B might have Instagram, LinkedIn, and Threads — completely separate accounts, completely separate content. Neither tenant can see or affect the other. This pattern repeats for every customer on your platform.

Tenants are your customers — the agencies, brands, or individuals paying for your product. Each tenant owns a set of connected social accounts and all the content created against those accounts.

Social accounts are the Instagram profiles, X accounts, LinkedIn pages, etc. that a tenant connects. These belong to the tenant, not to your platform. The tenant authorized your app to publish on their behalf through OAuth.

Content is posts, drafts, scheduled items, and queue entries. Every piece of content belongs to exactly one tenant and targets one or more of that tenant’s connected accounts.

The isolation rule is simple: a tenant can never see, modify, or publish to another tenant’s accounts or content. This must be enforced at the data layer, not just the UI layer. A frontend bug or a misconfigured API call should not be able to leak data across tenants.

In a relational database, this typically means every table that holds tenant-scoped data includes a tenant_id foreign key, and every query filters on it. In your API layer, the tenant is derived from the authenticated session — never from a request parameter that a caller could manipulate.


OAuth delegation: customers connect their own accounts

Your customers need to connect their Instagram, X, LinkedIn, TikTok, YouTube, Facebook, Threads, and Pinterest accounts to your product. This means OAuth — and each platform implements it differently.

The hard way: eight OAuth implementations

If you build OAuth directly, you are signing up for:

  • Meta (Instagram, Facebook, Threads): Token exchange with no refresh tokens. Short-lived tokens must be exchanged for long-lived tokens via a separate API call. Page tokens derived from user tokens never expire — if you follow the derivation chain correctly.
  • X: OAuth 2.0 with PKCE. Single-use refresh tokens — if you fail to store the new token after a refresh, the session is permanently lost.
  • LinkedIn: 60-day tokens. Refresh tokens only available to approved partners. Most developers get no programmatic refresh.
  • TikTok: 24-hour access tokens with 365-day refresh tokens, but the app audit process requires specific UX compliance.
  • YouTube: Google OAuth 2.0 with access_type=offline. Unverified apps are limited to 100 users.
  • Pinterest: 30-day access tokens with no grace period after expiration.

Each platform also requires a separate app review process before you can publish on behalf of users in production. Meta requires screencast submissions per permission scope. TikTok audits your UX. YouTube requires Google Cloud scope verification. These reviews take weeks and often require multiple rounds.

This is months of work before your first customer connects their first account. And it is ongoing maintenance — platforms change their OAuth behavior, deprecate scopes, and update API versions regularly.

The infrastructure approach: delegate OAuth to a publishing API

A publishing API like Postproxy has already passed app review on every platform and already implements every OAuth flow. Your product delegates account connection:

Terminal window
# When a customer clicks "Connect Instagram" in your UI
curl -X POST "https://api.postproxy.dev/api/profile_groups/grp_abc123/initialize_connection" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "instagram",
"redirect_url": "https://your-saas.com/settings/social/callback"
}'

The response contains an OAuth URL. You redirect the customer to it. They authenticate with Instagram. Instagram redirects back to your callback URL. A profile appears in the customer’s profile group.

Your customer never sees Postproxy. They see your product, then Instagram’s OAuth consent screen, then your product again.

The same endpoint works for all eight platforms — change the platform parameter. One integration, eight platforms connected.


Multi-tenant isolation with profile groups

Profile groups are the mapping between your tenants and the publishing infrastructure. Each tenant in your SaaS gets a profile group:

Terminal window
# When a new customer signs up
curl -X POST "https://api.postproxy.dev/api/profile_groups" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"profile_group": {"name": "Customer: Acme Corp"}}'

Store the returned id in your database alongside the customer record. Every API call for that customer uses their profile group.

For stronger isolation, generate a scoped API key per tenant. A scoped key can only access the profiles and content within its profile group — it cannot see other groups, create new groups, or access account-level resources. Even if your application code has a bug that passes the wrong tenant context, a scoped key physically cannot access another tenant’s data.

In your database, the mapping is straightforward: each customer record stores a profile_group_id and a scoped_api_key. Customer cust_001 maps to profile group grp_abc123 with scoped key sk_live_acme_.... Customer cust_002 maps to grp_def456 with sk_live_globex_.... Every API call for a given customer uses their scoped key, which locks access to their profile group and nothing else.

This gives you defense in depth. Your application enforces tenant isolation through session-based scoping. The publishing API enforces it again through scoped keys. A failure in one layer does not compromise the other.


The publishing layer: one API call, eight platforms

Once a customer has connected accounts, publishing from your product is a single request:

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer SCOPED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "New product drop. Link in bio."
},
"profiles": ["instagram", "twitter", "linkedin", "threads"],
"media": ["https://your-cdn.com/product-photo.jpg"],
"platforms": {
"instagram": {
"first_comment": "#newproduct #launch"
}
}
}'

The scoped API key ensures the post goes to the correct customer’s accounts. The platforms object lets your UI expose platform-specific options — Instagram first comments, YouTube titles and descriptions, TikTok privacy settings — without your backend implementing platform-specific logic.

What happens underneath is eight different publishing pipelines:

  • Instagram creates a media container, waits for processing, then publishes
  • LinkedIn uploads media separately with ETag tracking, then creates the post
  • X uses chunked uploads with INIT/APPEND/FINALIZE
  • YouTube uses resumable PUT uploads with quota management
  • Each platform has its own format validation, rate limits, and error handling

Your product does not touch any of this. It makes one HTTP request and gets back a post object with per-platform status.


Job queues for scheduled publishing

A scheduling feature is table stakes for social media SaaS. Your customers need to queue content for specific times, and many want recurring timeslots.

Scheduled posts

Pass a scheduled_at timestamp when creating a post:

{
"post": {
"body": "Monday morning motivation",
"scheduled_at": "2026-04-06T09:00:00-04:00"
},
"profiles": ["instagram", "linkedin"]
}

Postproxy holds the post and publishes at the specified time. Your product builds the calendar UI; the API handles the timer, the retry logic, and the platform-specific publishing flow when the time arrives.

Queue-based scheduling

For customers who want recurring timeslots — “post every weekday at 9 AM and 2 PM” — queues assign content to the next available slot automatically:

Terminal window
# Create a queue with recurring timeslots
curl -X POST "https://api.postproxy.dev/api/queues" \
-H "Authorization: Bearer SCOPED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queue": {
"name": "Weekday content",
"timeslots": [
{"day": "monday", "time": "09:00"},
{"day": "monday", "time": "14:00"},
{"day": "tuesday", "time": "09:00"},
{"day": "tuesday", "time": "14:00"},
{"day": "wednesday", "time": "09:00"},
{"day": "wednesday", "time": "14:00"},
{"day": "thursday", "time": "09:00"},
{"day": "thursday", "time": "14:00"},
{"day": "friday", "time": "09:00"},
{"day": "friday", "time": "14:00"}
]
}
}'

Then add posts to the queue instead of scheduling them individually:

{
"post": {
"body": "Content for the next available slot"
},
"profiles": ["instagram", "twitter"],
"queue_id": "queue_xyz789"
}

Queues support priority levels (high, medium, low) and jitter — a random offset of up to 60 minutes that makes automated posting patterns look less mechanical. Your customers configure their schedule in your UI; the queue engine runs on the infrastructure side.

This is one of the most complex pieces to build yourself. A reliable job scheduler needs persistent storage, clock-accurate triggers, timezone handling, retry logic for failed publishes, and graceful handling of timeslot conflicts. Delegating it to the publishing API removes an entire subsystem from your architecture.


Delivery confirmation with webhooks

When your customer schedules a post to go to Instagram, X, LinkedIn, and Threads, they need to know what happened on each platform. Did it publish? Did it fail? Why?

Polling GET /api/posts/:id works for an MVP, but a production SaaS needs real-time delivery confirmation. Postproxy sends webhooks for every publishing event:

  • platform_post.published — content went live on a specific platform
  • platform_post.failed — content failed on a specific platform, with the error reason
  • platform_post.failed_waiting_for_retry — content failed but will be retried automatically
  • profile.disconnected — a customer’s token expired or was revoked

Each event fires per platform, not per post. A post targeting four platforms generates four webhook deliveries. Your application knows exactly what succeeded and what needs attention.

Building a webhook handler

Your webhook endpoint receives events and updates your application state:

@app.route("/webhooks/postproxy", methods=["POST"])
def handle_webhook():
event = request.json
if event["type"] == "platform_post.published":
# Update post status in your database
update_platform_status(
post_id=event["data"]["post_id"],
platform=event["data"]["platform"],
status="published",
platform_url=event["data"]["url"]
)
# Notify the customer
notify_customer(event["data"]["post_id"], "published", event["data"]["platform"])
elif event["type"] == "platform_post.failed":
update_platform_status(
post_id=event["data"]["post_id"],
platform=event["data"]["platform"],
status="failed",
error=event["data"]["error"]
)
notify_customer(event["data"]["post_id"], "failed", event["data"]["platform"])
elif event["type"] == "profile.disconnected":
# Mark the profile as needing reconnection in your UI
mark_profile_expired(event["data"]["profile_id"])
return "", 200

This is the backbone of your SaaS’s reliability story. Your customers trust your product to publish their content. Webhooks give you the real-time feedback loop to show them exactly what happened — and to alert them when something needs their attention, like a disconnected account.


Draft and approval workflows

Many social media SaaS products serve teams, not individuals. A content creator drafts a post. A manager reviews it. A compliance officer approves it. Then it publishes.

The draft system provides the hold-and-release mechanism:

{
"post": {
"body": "Q2 earnings highlight: revenue up 23% YoY",
"draft": true
},
"profiles": ["linkedin", "twitter"],
"media": ["https://your-cdn.com/q2-earnings.png"]
}

The post is created but not published. It sits in draft state until your application triggers publication:

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts/post_abc123/publish" \
-H "Authorization: Bearer SCOPED_API_KEY"

Your SaaS owns the approval logic — who can approve, how many approvals are needed, escalation rules, notification chains. The API provides the mechanism to hold content until your workflow says “go.” This separation means you can build arbitrarily complex approval workflows without the publishing infrastructure knowing or caring about your business rules.

For a deeper look at approval patterns, see content approval workflows for automated social media publishing.


Analytics: cross-platform metrics per tenant

Your customers want to know how their content performed. Not just “it published” but impressions, likes, comments, shares — and they want it in one place, not scattered across eight platform dashboards.

The stats endpoint returns engagement data across platforms:

Terminal window
curl "https://api.postproxy.dev/api/posts/stats?post_ids=post_abc,post_def" \
-H "Authorization: Bearer SCOPED_API_KEY"

Metrics are broken down by platform:

PlatformAvailable metrics
Instagramimpressions, likes, comments, saved, profile_visits, follows
Facebookimpressions, clicks, likes
X (Twitter)impressions, likes, retweets, comments, quotes, saved
LinkedInimpressions
TikTokimpressions, likes, comments, shares
YouTubeimpressions, likes, comments, saved
Threadsimpressions, likes, replies, reposts, quotes, shares
Pinterestimpressions, likes, comments, saved, outbound_clicks

Snapshots are stored over time, so you can build trend charts. The scoped API key ensures you only see metrics for the current tenant’s content.

This is the data layer behind your SaaS’s reporting features — performance dashboards, exportable reports, engagement alerts. You build the visualization; the API normalizes the underlying platform metrics into a consistent format.


Usage metering and billing

If you are building a SaaS product, you are billing customers. Social media SaaS products typically meter one or more of:

  • Connected accounts. How many social profiles the customer has connected.
  • Published posts. How many posts the customer published in the billing period.
  • Team seats. How many users on the customer’s team can access the product.

The publishing API gives you the data for the first two. Connected accounts are tracked per profile group — call GET /api/profiles with a scoped key to get the count. Published posts are tracked per profile group through the post history endpoint.

Your billing logic sits in your application:

def calculate_monthly_usage(customer_id):
scoped_key = get_scoped_key(customer_id)
# Count connected profiles
profiles = postproxy_client.list_profiles(api_key=scoped_key)
connected_accounts = len([p for p in profiles if p["status"] == "active"])
# Count posts published this month
posts = postproxy_client.list_posts(
api_key=scoped_key,
created_after=start_of_month(),
created_before=end_of_month()
)
published_posts = len([p for p in posts if p["status"] == "published"])
return {
"connected_accounts": connected_accounts,
"published_posts": published_posts
}

This is your SaaS’s billing meter. The publishing API does not bill your customers — it bills you. You decide how to package and price the value for your customers. Some SaaS products charge per connected account. Others charge per post. Others bundle publishing into a flat monthly fee. The architecture supports all of these because usage data is available per tenant.


The architecture, assembled

Here is the full stack for a production social media SaaS:

The stack has three layers. At the top is your SaaS product — authentication and tenancy, the content editor and calendar, analytics and reporting, billing and usage metering. These modules all talk to your backend API layer, which is the single integration point with the layer below. Your backend calls the Postproxy API for everything publishing-related. Postproxy, in turn, fans out to all eight social platforms: Instagram, Facebook, X, LinkedIn, Threads, TikTok, YouTube, and Pinterest.

You build: Authentication, tenant management, the content editor, the scheduling calendar, the analytics dashboard, the billing system, the approval workflows. Everything your customers interact with.

Postproxy handles: OAuth for eight platforms, app review compliance, eight publishing pipelines, token lifecycle management, media transcoding, format validation, delivery confirmation, queue scheduling, and engagement metrics normalization.

The boundary is clean. Your product is everything above the publishing API. The publishing API is everything below. Your customers never see the infrastructure layer.


What this architecture gets you

Building a social media SaaS on a publishing API is not about saving a few weeks of development time. It is about what you do not have to maintain for the life of your product.

You do not maintain eight OAuth implementations that break when platforms change their token behavior, deprecate scopes, or update API versions. LinkedIn deprecated r_liteprofile in 2023. Instagram sunset the Basic Display API in 2024. Facebook Graph API versions deprecate every two years. Each change requires engineering work — unless the infrastructure layer absorbs it.

You do not go through app review on eight platforms. Meta’s review process alone — screencast submissions per permission scope, reviewer feedback loops, resubmissions — takes weeks. TikTok’s UX audit requires specific UI elements. YouTube’s scope verification requires a Google Cloud project review. These are not one-time costs. Platform policy changes can trigger re-review.

You do not build eight media upload pipelines. Instagram’s container model, LinkedIn’s ETag-tracked chunked uploads, X’s INIT/APPEND/FINALIZE, YouTube’s resumable PUT, TikTok’s server-to-server transfer — these are not variations of the same pattern. They are distinct implementations that each need their own error handling, retry logic, and status polling.

You do not manage token lifecycles. Expired tokens mean silently broken features. A customer’s “Publish to Instagram” button stops working with no error message. Your support team is now debugging OAuth token states instead of building features.

The build-vs-buy analysis is even more stark for a SaaS product than for a single-brand tool. A SaaS multiplies every maintenance burden by the number of customers. One token expiration bug affects every customer on the platform. One API deprecation requires migration for every connected account.


Getting started

The fastest path from idea to working product:

  1. Create a Postproxy account and get your API key from the dashboard.
  2. Create a profile group for your first test tenant.
  3. Connect a social account using Initialize Connection — see the white label guide for the full OAuth delegation flow.
  4. Publish a test post with one API call.
  5. Set up a webhook endpoint to receive delivery confirmations.

From there, build your product’s UI on top of the API. The React dashboard tutorial walks through a working implementation. The multi-brand architecture guide covers scaling to hundreds of tenants.

Your SaaS product is the experience your customers pay for. Postproxy is the publishing infrastructure underneath. Build the product. Delegate the plumbing.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.