API GuidesAugust 5, 202611 min readingMarcel Czuryszkiewicz

Social Media Posting API: What Runs Under the Hood, and When to Build Your Own

A social media posting API hides four different publishing models behind one endpoint. This article opens them up: container flows, chunked uploads, pull-from-URL, non-idempotent publishes that duplicate on retry - and a list of conditions under which you should build it yourself.

A social media posting API is one HTTP endpoint that publishes to many networks. What it hides is that there is no single way to publish. Across the 15 platforms we run in production, a "post" resolves into four incompatible flows: a single JSON call, a two-step container, a chunked byte upload, and a pull-from-URL job you have to poll. The endpoint is the easy part. The four flows underneath it are where the work is.

TL;DR

  • One endpoint, four flows. Single-shot JSON (Slack, Discord, Google Business), container create-then-publish (Instagram, Threads), chunked upload (X, LinkedIn, YouTube, Facebook, Snapchat), pull-from-URL with polling (TikTok).
  • Validation fails before the network does. Bluesky caps images at 2 MB, Facebook at 4 MB, Instagram at 8 MB. Bluesky text at 300 characters, Threads at 500, LinkedIn at 3000.
  • A retry can double-post. Reddit and Facebook video publishes are not idempotent in our stack, so both run with maximumAttempts: 1 instead of the default 3.
  • Error classification is the real product. Our retryability table carries 248 entries across 24 constants - 105 of them TikTok alone.
  • Build it yourself if you need one or two platforms, control the accounts, and can afford the app-review calendar. Criteria below, before the pitch.
Railway tracks photographed from above, several parallel lines running through a set of switches where the rails cross
One request goes in. Which rails it takes is decided further down.

The four publishing models behind one endpoint

Every platform looks identical from inside a unified request body. Route it, and that stops immediately.

ModelPlatforms (our routing)What the caller has to survive
Single-shot JSONSlack, Discord, Google Business, RedditOne call, one response. Reddit adds a single-use WebSocket to learn the permalink.
Container: create then publishInstagram, ThreadsTwo calls with a media-processing wait between them, keyed by creation_id.
Chunked / resumable byte uploadX, LinkedIn, YouTube, Facebook, SnapchatINIT/APPEND/FINALIZE or a resumable session, plus offset bookkeeping if the worker dies.
Pull-from-URL, then pollTikTokYou hand over a URL; the platform fetches it on its own schedule and you poll for the verdict.

Eight of the fifteen - Instagram, Threads, Mastodon, Pinterest, LinkedIn, Facebook, Bluesky and X - run a submit → wait → publish path rather than a single activity, because the media is not ready when the create call returns.

The container model is the one most people meet first. Instagram wants a media object created, then published separately:

# 1. create the container
curl -X POST "https://graph.facebook.com/v23.0/${IG_USER_ID}/media" \
  -d "image_url=https://cdn.example.com/asset.jpg" \
  -d "caption=Ship it" \
  -d "access_token=${TOKEN}"
# -> { "id": "17..." }   this is the creation_id, NOT a post

# 2. publish it, after the container reports FINISHED
curl -X POST "https://graph.facebook.com/v23.0/${IG_USER_ID}/media_publish" \
  -d "creation_id=17..." \
  -d "access_token=${TOKEN}"

Between those two calls you need a poll loop with a budget. Ours polls media status on a [15s, 15s, 20s] schedule with a 10-minute ceiling.

Chunked upload is a different shape of the same patience, and chunk sizes are neither negotiable nor consistent: X uses 5 MB per APPEND, LinkedIn 4 MB per PUT with ETags collected for the finalize call, YouTube 8 MB against a resumable session URI, Snapchat 32 MB with a hard cap of 35 chunks.

TikTok is its own category. Our integration submits a video_url and TikTok fetches the bytes itself, which moves the failure mode from your network to their crawler. If TikTok cannot prove you own the host it fetches from, the publish fails permanently - our classifier matches the phrase review our url ownership verification rules and marks that error terminal, because no retry budget fixes a domain that was never verified in the developer portal. That verification is a review process, not a code change; we wrote up what it costs in the TikTok API approval guide.

TikTok publishes also stay pending far longer than anything else we handle, so the poll schedule stretches: 30s, 60s, 2 min, 5 min, 15 min, 30 min, 1 h, 4 h, against a 24-hour budget. Any posting API that models publishing as request/response will report those as failures.

Media validation is where posts die before they leave your server

The cheapest error is the one you raise locally. These are the limits our validation service enforces before a single byte goes out, verified 4 August 2026:

PlatformMax imageMax videoText limit
Bluesky2 MB100 MB300
Facebook (post)4 MBnot set50000
X5 MB512 MB280 free/basic, 25000 premium
Pinterest5 MB2 GB100 title / 800 description
LinkedIn5 MB2 GB3000
Instagram (post)8 MBnot set2000
Threads8 MB1 GB500
TikTok20 MB (images)1 GB2200
Discord25 MB25 MB2000
YouTuben/a256 GB100 title / 5000 description
Google Business5 MBn/a1500
Snapchat100 MB100 MB160

The video spread covers four orders of magnitude, from Discord's 25 MB to YouTube's 256 GB, so there is no safe default to pick once. The text limits are not a formatting concern either: a 400-character caption is fine on Instagram and a hard rejection on Bluesky.

Attachment counts add another axis - X and Bluesky take 4 images, Instagram and LinkedIn 10, Pinterest and Google Business exactly 1 - and TikTok rejects images above 1920×1080 unless auto-scaling is on. Full matrix, with durations and MIME types, in our media requirements reference.

Two identical white-framed windows side by side on a plain yellow wall
A retry on an ambiguous timeout is how you get the same post twice.

A retry is not free: publishing is often not idempotent

This is what separates a posting API from a wrapper around fifteen SDKs. Most HTTP clients retry on timeout. For publishing, a timeout is ambiguous: the post may already exist. Our workflow encodes that ambiguity as explicit policy rather than a global default.

PolicyAttemptsFirst backoffApplies to
Default publish33 min, ×2Most platforms
Upload330 s, ×2, 2 min heartbeatResume-safe byte uploads
No auto-retry1-Reddit
No retry on media publish1-Facebook video

Reddit gets one attempt because each /api/submit creates a new post and hands back a single-use WebSocket - a retry after a WebSocket timeout produces two posts, not one. Facebook video gets one attempt because start, chunk, finish and create all run inside a single publish step with no idempotency key, so a worker crash mid-flow would double-post. Discord only retries a send when the webhook itself is gone (error 10015 or a 404); any other failure might mean the message already landed.

YouTube is the counter-example that proves the rule: because we persist the resumable session URI, a retry continues the upload from the last committed offset instead of starting a second video. Mastodon is the only platform in our set that accepts a real Idempotency-Key header, and we send the post ID as the key.

The same problem shows up at the socket level. We deliberately keep EPIPE out of the transient-network set - a broken pipe can happen mid-write on a non-idempotent POST, and calling it retryable trades a visible failure for an invisible duplicate.

Classification itself is a data problem. Our retryability table holds 248 entries across 24 constants: 105 TikTok codes and patterns, 43 Meta codes and subcodes, 20 Google reasons, 10 each for X and Pinterest, 7 each for LinkedIn and Discord. Some encode a business rule rather than a network condition - Instagram subcode 2207042 is a rolling 24-hour publishing cap, marked terminal on purpose, because failing fast beats burning a retry budget on a daily quota. The full taxonomy is in our error handling guide.

A workshop wall rack holding rows of chisels, hand planes, saws and screwdrivers, each tool in its own slot
Sometimes building it yourself is the right call. Here is when.

When you should build it yourself

A unified layer is not automatically correct. Build direct integrations when these are true:

  • You need one or two platforms and expect that to hold. A single X or LinkedIn integration is about a week of work plus review. A unified layer only pays back around platform four or five.
  • You control the accounts. Publishing to your own company pages means one token set and one OAuth app - no multi-tenant token storage, no per-customer reconnect flow.
  • Your volume is low enough that rate limits never bind. Publish a handful of times a day and the rate-limit models that make fleet-scale publishing hard will never fire for you.
  • You need a surface no vendor exposes. Ad creatives, DM automation, an unusual video pipeline. Anything at the edge of a platform's API arrives in your own integration first.
  • App review is on your calendar anyway. TikTok, Meta and X all gate publishing behind review; if you are going through it for another reason, the marginal cost of doing it yourself drops sharply.
  • The failure modes are acceptable. If a missed post costs an apology rather than a customer, you do not need retry classification or duplicate protection.

The inverse is the test for buying: more than four platforms, publishing on behalf of other people's accounts, and a dropped or duplicated post that becomes a support ticket. Just do not estimate it as "it's just HTTP calls" - it is HTTP calls plus per-platform token refresh, four publishing models, 248 error classifications, and a review process for each.

If you buy: what to test in the first hour

Vendor landing pages converge on the same claims. Test behaviour instead.

  1. Post an oversized asset. A 30 MB image to Bluesky should be rejected locally with a clear message, not accepted and failed silently 40 seconds later.
  2. Ask what happens on a publish timeout. If the answer is "we retry", ask which platforms are excluded and why. A vendor that cannot name Reddit or an equivalent has not hit it yet.
  3. Publish to TikTok and watch the status. An immediate success rather than a pending state means the platform is not being modelled.
  4. Revoke a token in the platform UI. You should get a specific reconnect signal, not a generic 400.
  5. Read the error payload. Preserved platform code and subcode, or everything flattened into {"error": "failed"}? You need the original to open a ticket with the platform.

For the wider picture - OAuth flows, scopes, token lifetimes, analytics normalisation - our social media API integration guide covers the whole surface across the same 15 platforms.

Where bundle.social fits

We built the layer described above because we had to. bundle.social exposes one publishing endpoint over 15 platforms and handles the parts this article spent its length on: routing each post to the right publishing model, validating media against per-platform limits before upload, classifying errors into retryable and terminal, and refusing to auto-retry the publishes that would duplicate. Async platforms are modelled as async - a TikTok post reports pending until TikTok says otherwise. Around 10 million posts a month run through it, roughly 97% succeeding on the first attempt.

It is not the right tool if you need one platform, or if you need ad-creative or DM endpoints - build those directly. If you want the transactional detail, endpoints, and limits, that lives on the social media posting API page rather than here.

Frequently asked questions

What is a social media posting API?

It is an interface that publishes content to social networks programmatically. Two kinds exist: native platform APIs, where each network has its own endpoints, auth and media rules, and unified APIs, which put one endpoint in front of many networks. The translation is real work - the same "post" maps to four different publishing models across the 15 platforms we support.

Can one API call really post to every platform?

The call can be one. The execution is not. Behind a single request our workflow runs a single-shot JSON post for Slack, a container create-then-publish for Instagram, a chunked upload for LinkedIn, and a pull-from-URL job for TikTok. A unified API gives you one request shape and one error format - not one underlying flow.

Why do posts fail even when the API returns 200?

Because most platforms accept content before they process it. Instagram returns a creation_id, not a post. TikTok fetches your video after responding. The verdict arrives from a status poll or webhook minutes or hours later - our TikTok polling budget runs to 24 hours. Treat a 200 as "accepted", never as "published".

How long does it take to build a posting integration yourself?

The code for one platform is roughly a week. The calendar is set by app review, which TikTok, Meta and X all require for publishing scopes and which runs in weeks. Multiply by platform count, then add maintenance: every media limit, error code and endpoint here can change without notice.

What breaks most often in production?

Media validation and token expiry, in that order. Media limits differ by four orders of magnitude across platforms and are enforced silently by some networks. After that, retry logic: a naive retry on an ambiguous timeout produces duplicate posts on any platform without an idempotency key, which in our set is 14 out of 15 - only Mastodon accepts one.

Marcel Czuryszkiewicz
Written by

Marcel Czuryszkiewicz

Co-Founder

Marcel is one of the builders behind bundle.social, creating a social media API with a strong focus on developer experience, reliability, and real support. Before bundle.social, he worked at Samsung R&D, Reply AI, and Docplanner, building internal tools that helped companies grow. He brought that same practical, product-focused mindset to bundle.social, so you can trust that your project is in good hands.