API GuidesAugust 4, 202610 min readingMarcel Czuryszkiewicz

Social Media API Error Handling

Every social media API failure reduces to one question: retry, or give up. Get it wrong one way and you publish twice; the other way you fail a post that would have worked. Covers classification for 15 platforms, which ones duplicate on retry, and failures reported after the post went live.

Social media API error handling comes down to one question asked over and over: retry, or give up. Answer it wrong in one direction and you publish the same post twice to a customer's audience. Answer it wrong in the other and you fail a post that would have succeeded on the second attempt. This is a classification problem, not a networking problem, and the classification is different for every platform.

A railway junction photographed from above, several tracks crossing and diverging through a set of switch points
There is no third option, and the default is wrong.

The only question that matters

A publish failed. You have a status code, maybe a platform-specific error code, maybe a string. You have to decide, right now, whether to try again.

The two mistakes are not symmetrical.

Retrying a terminal error is cheap but noisy. You burn rate limit, you delay the eventual failure notification, and the customer waits longer to learn something you already knew. Annoying, recoverable.

Retrying a non-idempotent success is expensive. The platform accepted the post, the response never reached you, and you post again. The customer's followers see it twice. That is a support ticket, a refund conversation, and on some platforms a spam flag on the account.

So the bias is not "retry aggressively". The bias is know which operations are safe to repeat, and be conservative everywhere else.

Three buckets

A wall of numbered brass post office boxes with combination dials, every item destined for exactly one box
Triage first. Everything else follows from it.

Every error goes into one of three categories, and the third is the interesting one.

BucketMeaningAction
RetryableTransient. The same request will probably work laterRetry with backoff, up to a budget
TerminalThe request will never succeed as-isFail immediately, tell the customer why
UnknownUnclassified. New code, changed message, undocumented conditionTreat as retryable, but only if the operation is idempotent

The rule for unknown is the whole design. If you treat everything unclassified as retryable, you will eventually duplicate a post. If you treat everything unclassified as terminal, every platform change becomes an outage. The resolution is that idempotency decides, not the error.

Two lists cover most of the transport layer regardless of platform:

const RETRYABLE_HTTP_STATUS_CODES = new Set([
  408, 425, 429, 500, 502, 503, 504, 520, 522, 524,
]);

const NETWORK_TRANSIENT_CODES = new Set([
  "ETIMEDOUT", "EAI_AGAIN", "ECONNRESET", "ECONNABORTED",
  "ENETUNREACH", "EHOSTUNREACH", "EADDRNOTAVAIL", "EADDRINUSE", "ENETDOWN",
]);

One deliberate omission: EPIPE is not in that list. A broken pipe can happen mid-write on a POST that the server already accepted. Classifying it as blanket-retryable is exactly how you double-post. Handle it per caller, only where the operation is idempotent.

Classification by platform

The transport layer is the easy part. Platform error codes are where the work is. This is a condensed view of what we classify in production, assembled over three years of watching these APIs fail and correcting the table each time one of them surprised us. Meta's error reference is the closest any platform comes to publishing its own version.

PlatformRetryable examplesTerminal examples
Meta (Facebook, Instagram, Threads)#2 API service, #4 too many calls, #9 rate limit, #17 user too many calls, #341 app limit. Subcodes 1390008 posting rate, 1363047/1363048 video upload retry, 2207003 media fetch timeout#3 capability, #10 permission denied, #25 account restricted, #100 invalid parameter, #190 token, #200 permission, #324 invalid media, #506 duplicate post. Subcodes 463 expired, 460 password changed, 492 role lost, 2207042 the 24-hour publishing cap
TikTok40016/40100 rate limit, 40201 task not ready, 40901 transcoding, 40902 cannot fetch URL, 50000 system error, 50002 server busy, 50012 timeout, 60001 maintenance40001 no permission, 40102 token expired, 40103 refresh expired, 40907 file too large, 40915 file does not meet specs, plus the whole spam_risk_* family
X429 and 5xxSuspended account, locked account, duplicate content, invalid media ids, video too long, codes 64, 89 and 326
LinkedIn429 and 5xxInsufficient permissions, forbidden resource, duplicate content, member restricted, revoked token, document or image processing failure
Pinterestcodes 8, 12, 30, 2787codes 2, 58, 2786, plus "board name already exists" and "unable to reach the URL"
Google (YouTube, Business Profile)rateLimitExceeded, userRateLimitExceeded, backendError, internalError, deadlineExceeded, serviceUnavailablequotaExceeded, forbidden, authError, insufficientPermissions, invalidTitle, uploadLimitExceeded, youtubeSignupRequired
Reddit429 and 5xxNO_IMAGES, NO_TEXT, SUBMIT_VALIDATION_FLAIR_REQUIRED, SUBREDDIT_NOTALLOWED
Discord429 and 5xx40001, 50001, 50013, 50014, 50025, 50041, 10015

Three rows in there are counterintuitive enough to call out.

Google's quotaExceeded is terminal, but rateLimitExceeded is retryable. They read like synonyms. They are not. Rate limit means you went too fast and can slow down. Quota exceeded means you spent the day's allocation, and on YouTube that resets at midnight Pacific. No backoff within a job's lifetime survives that.

Meta's 2207042 is the Instagram 24-hour publishing cap, and it is terminal on purpose. It is a rolling limit of 25 posts per 24 hours. No retry budget can outlast it, so parking the post in a retry state for hours just delays a failure the customer needs to know about now.

Pinterest error 30 is retryable and error 2 is not, and neither is self-describing. This is the general shape of the problem: the codes carry no semantics you can infer. You either maintain the table or you guess.

Some classification is inevitably string matching, because platforms return prose. Regex on error messages is fragile and we treat it as a fallback, not a foundation: an upstream wording change silently reclassifies an error, so anything matched by pattern should be logged loudly enough that you notice the drift.

Where a retry duplicates the post

This is the table that should drive your retry policy, and almost nobody publishes it.

Platform / operationSafe to retry?Why
Reddit submitNoEvery /api/submit creates a new post and a single-use WebSocket. A WebSocket timeout that you retry produces a second post
Facebook video and ReelsNoThe start, chunk, finish and create sequence has no idempotency key. A crash mid-sequence retried from the top produces a second video
YouTube uploadYesResumable upload. The session URI is persisted, so a retry continues the existing upload rather than starting a new one. No duplicate
Instagram container createYesCreating a container does not publish anything. Worst case you create an orphan container that expires in 24 hours
Instagram media publishCarefulPublishing a container twice can return an error while the post is already live. See the next section
X post createCarefulX rejects duplicate content, which accidentally protects you, but the rejection is terminal and looks like a failure

Our retry policies follow directly from that table. A normal publish gets 3 attempts with a 3-minute initial interval and exponential backoff. A YouTube upload gets 3 attempts, a 60-minute budget and a 2-minute heartbeat, because a large video legitimately takes longer than a publish and a dead worker needs detecting in minutes rather than at the end of the hour. Reddit and Facebook video get exactly one attempt, and the first failure is final.

That last one feels wrong the first time you write it. A single transient network blip fails the post permanently. It is still correct, because the alternative is a customer's audience seeing the same video twice and no way to tell which failure caused it.

bundle.social

Every publish failure comes back classified. You decide what the customer sees.

Retryable or terminal, with a message you can put in front of a customer.

When the failure is a lie

Some errors arrive after the platform already did the thing.

The pattern: your request times out or returns a 5xx, but the post is live. This happens most often on Instagram's media_publish step and on any platform where the publish is slow enough for a proxy timeout to fire before the response comes back.

If you retry blindly, you duplicate. If you fail blindly, the customer sees "failed" next to a post that is visibly on their profile, which is worse for trust than a plain failure.

The fix is a verification step before the retry:

async function publishWithVerification(account, post) {
  try {
    return await platform.publish(account, post);
  } catch (err) {
    if (!isRetryable(err)) throw err;

    // Before retrying, ask the platform what it actually has.
    // Look for something that identifies THIS post, not just "any recent post".
    const existing = await platform.findRecentPost(account, {
      since: post.attemptStartedAt,
      idempotencyHint: post.clientReference,
    });

    if (existing) {
      // The failure was a lie. Record the success and stop.
      return { id: existing.id, permalink: existing.permalink, recovered: true };
    }

    throw err; // Genuinely failed. Let the retry policy handle it.
  }
}

Two things make this work in practice. Carry your own reference into the post where the platform allows it, even if it is only in internal metadata, so the lookup is exact rather than heuristic. And window the lookup to the current attempt, or a customer who legitimately posted something similar five minutes ago gets matched to the wrong item.

Not every platform gives you a usable lookup. Where it does not, the honest answer is to lean terminal and let the customer decide, rather than guessing.

Backoff that means something

The generic advice is "use exponential backoff". Here is what the numbers actually need to be.

Match the initial interval to the failure you are backing off from. A network blip clears in seconds. Media transcoding takes minutes. Meta's posting rate limits clear in tens of minutes. Starting at 3 minutes with a coefficient of 2 gives you 3, 6, 12 minutes, which covers transcoding and most rate limits without holding a worker slot for an hour.

Cap total attempts low. Three is enough for genuinely transient errors. Anything that survives three attempts over 20 minutes is not transient, it is a problem someone needs to look at.

Some failures deserve a much longer park. TikTok's publish confirmation can take up to an hour for large files, so the fallback path there waits 4 hours between attempts rather than 3 minutes. Retrying a slow platform fast is just a way of failing faster.

Never retry into a rolling cap. Instagram's 25 posts per 24 hours, YouTube's daily quota, TikTok's "too many pending shares". Fail fast, surface it, and let scheduling handle it. Sitting in a retry loop against a 24-hour window is a worker held hostage for a day.

Related: the rate limits for every platform determine most of these numbers, and a large share of "errors" are actually media that never should have been sent, which is covered in media requirements per platform.

The shorter path

Roughly two hundred error codes across fifteen platforms, a retry policy per operation rather than per platform, an idempotency table you have to derive by breaking things, and a verification step for the failures that are not failures. It is a lot of machinery to keep a scheduled post from going out twice.

bundle.social's error reference documents what each code means and what to do about it. The classification above runs on our side, built from three years of production failures across every platform we support, so a terminal error fails immediately with a message you can show a customer instead of sitting in a retry loop for twenty minutes on its way to the same outcome.

Frequently asked questions

Which social media API errors should I retry?

Transport-level failures (408, 425, 429, 5xx, and network timeouts) plus platform codes explicitly documented as transient, such as Meta #4 and #9 or TikTok 40901. Everything about permissions, tokens, invalid parameters, media validation and duplicate content is terminal and will fail identically on the second attempt.

How do I avoid double-posting when I retry?

Decide per operation, not per platform. Retry only where the operation is idempotent or resumable, such as YouTube's resumable upload. Where it is not, such as Reddit submit or Facebook video, allow exactly one attempt and treat the first failure as final.

What do I do with an error code I have never seen?

Treat it as retryable only if the operation is safe to repeat, and log it prominently. Unknown codes are the main way platform changes reach you, and the log is how you notice before customers do.

Why did my post publish even though the API returned an error?

Because the platform completed the work and the response was lost, usually to a timeout. Before retrying a transient failure, query the platform for a post matching the current attempt. If it is there, record the success rather than trying again.

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.