API GuidesAugust 4, 202610 min readingMarcel Czuryszkiewicz

OAuth Token Refresh for Social Media APIs

TikTok tokens last a day, Facebook gives you sixty, and X with offline access effectively never expires. Covers the three expiry models across 15 platforms, which ones rotate the refresh token and lock you out permanently if you drop it, and the four reasons a valid token still stops working.

OAuth token refresh looks like one problem and is actually three. Tokens expire on wildly different schedules across social media APIs: TikTok gives you 24 hours, Facebook gives you 60 days, X with offline.access effectively gives you forever. Some platforms rotate the refresh token on every use, and losing that new value locks the account out permanently. And a token can stay perfectly valid while the access behind it disappears. This covers all three.

Three expiry models

Every social platform picks one of these, and the choice determines how much machinery you need.

Short-lived access token plus refresh token. The standard OAuth 2.0 arrangement. The access token lives hours, the refresh token lives months, and you trade one for the other. TikTok, Discord, Google (YouTube and Business Profile), Pinterest, Reddit and Bluesky work this way. This model is the most work and the most predictable.

Long-lived token with no refresh token. Meta's approach on Facebook, Instagram and Threads. You exchange a short-lived token for a long-lived one that lasts around 60 days, and you extend it by exchanging it again before it dies. There is no separate refresh credential, which means if you let it expire you cannot recover without the user. No grace period, no admin fix.

Effectively permanent. X with the offline.access scope issues a refresh token that does not expire on a schedule. We store the access token with a sentinel far-future expiry and refresh on demand rather than on a clock. LinkedIn sits between models: the access token lasts 60 days and refresh behaviour depends on the product approval.

The practical consequence: you cannot write one scheduler. A daily job is far too slow for TikTok and absurdly wasteful for X.

Token lifetimes by platform

Verified 02.08.2026. Where a platform does not publish a number, we say so.

PlatformAccess tokenRefresh tokenRotates refresh?Notes
Facebook~60 daysnonen/aExtend by re-exchanging the long-lived token. Page tokens can be non-expiring
Instagram (Facebook Login)~60 daysnonen/aInherits the Facebook user token lifetime
Instagram (Instagram Login)~60 daysnonen/aRefresh endpoint requires the token to be at least 24 hours old
Threads~60 daysnonen/aSame model as Instagram Login
TikTok24 hours365 daysYesShortest access token of any platform here
Xno fixed expiry with offline.accesslong-livedYesWe store a sentinel expiry and refresh reactively
LinkedIn~60 daysproduct-dependentNoRefresh tokens require an approved product
YouTube / Google1 houruntil revokedNoRefresh token issued only on first consent with access_type=offline
Google Business Profile1 houruntil revokedNoSame Google OAuth stack
Pinterest30 days1 yearNo
Reddit1 houruntil revokedNoRequires a descriptive User-Agent or requests are throttled
Discord7 daysuntil revokedYes
Blueskyshort, minutessession-basedYesAT Protocol sessions, not classic OAuth semantics
Mastodonno expiry by defaultn/an/aInstance-dependent; some instances do expire
Slackno expiry by defaultn/an/aToken rotation is opt-in per app
Snapchat1 houruntil revokedNoStandard refresh grant, short access token like Google

Two rows deserve attention. TikTok at 24 hours means a customer who connects on Friday and does not publish until Monday needs a refresh in between, done by your infrastructure, unprompted. Google at 1 hour is short but forgiving, because the refresh token lives until revoked. TikTok is short and rotating, which is the hardest combination.

Refresh token rotation

Runners photographed from directly above on a red running track, strung out in a diagonal line across the lanes with long shadows behind them
One token replaces the last, over and over. Break the chain once and it does not restart.

This is where integrations lose accounts permanently, and it is worth being blunt about the mechanism.

On a rotating platform, the refresh response contains a new refresh token, and the old one is invalidated the moment the new one is issued. If you do not persist the new value, the account is unrecoverable without user re-authorisation. Not degraded. Gone.

The failure is almost always in the same place: a conditional write.

// Wrong. The response may omit refresh_token on a non-rotating platform,
// but on a rotating one this drops the only valid credential you will ever get.
if (response.data.refresh_token) {
  await save({ refreshToken: response.data.refresh_token });
}
await save({ accessToken: response.data.access_token });

Two separate writes. If the second succeeds and the first was skipped, or the process dies between them, you now have a fresh access token and a dead refresh token. The account works for the next 24 hours and then stops forever.

// Right. One atomic write, and the refresh token is only overwritten
// when the platform actually sent a new one.
await saveAtomic({
  accessToken: encrypt(response.data.access_token),
  expiresAt: dateFromNow(response.data.expires_in ?? 3600),
  ...(response.data.refresh_token
    ? { refreshToken: encrypt(response.data.refresh_token) }
    : {}),
});

Three rules that follow from this:

Write both tokens in one transaction. Never in two statements, never across two services.

Do not delete the old refresh token until the new one has been used successfully at least once. Some platforms have a short overlap window. Using it is free insurance.

Log the rotation, not the token. When an account dies mysteriously, the question is always "did we get a new refresh token and lose it, or did the platform reject us?" A log line with the account id and a hash prefix answers that in seconds.

Refresh is not the same as validity

A brightly painted street meter against palm trees, labelled NOT for parking and asking for spare coins for charity
It looks exactly like a parking meter. It will not buy you a space.

A token that has not expired can still be useless. These are four different problems with four different fixes, and treating them as one is why customers get "reconnect your account" for something that reconnecting will not fix.

SymptomWhat actually happenedFix
Token valid, calls return #463Token expired despite your clock saying otherwiseRefresh, or re-authorise if there is no refresh token
Token valid, calls return #460The user changed their password, which invalidates sessionsOnly the user can fix this. Prompt re-authorisation
Token valid, calls return #492The user lost their admin role on the PageNot a token problem. Tell the customer who to re-add, and to which Page
Token valid, calls return #458 or #459Authorisation revoked, or a login checkpoint is requiredRe-authorisation, and for checkpoints the user must clear it on the platform first

The third row is the one that generates the most misdirected support. Your UI says "reconnect Facebook", the user reconnects, it fails again, because the problem is a permission on the Page rather than anything in your database. The message has to name the Page and say "ask an admin to give you a role", or the loop never ends.

The equivalent on the other platforms: LinkedIn returns 403 for a missing org role and for a missing scope with different message text, and Google returns authError for both a revoked grant and a suspended account. Both cases need the message parsed, not just the status code.

bundle.social

Fifteen refresh policies, and one webhook for the moment an account actually dies.

Hosted OAuth, token refresh, and a webhook for the moment an account actually dies.

When to refresh

Two strategies, and the right answer is usually both.

Refresh in the background, on a schedule. A job that scans for tokens expiring within a threshold and refreshes them. Pick a threshold well ahead of expiry: a quarter of the token's lifetime is a reasonable rule, so 6 hours for TikTok and about two weeks for Meta. The advantage is that publish time never pays for a refresh. The disadvantage is that a broken refresh is discovered by your monitoring rather than by an actual failure, which is also the point.

Refresh reactively, before a call that needs it. Check expiry, refresh if close, then publish. This is mandatory as a safety net regardless of what your scheduler does, because a token can be invalidated between scheduler runs.

What breaks if you only do the first: an account connected 30 seconds before a publish has no scheduler run behind it yet. What breaks if you only do the second: a customer who does not publish for a week comes back to a dead TikTok connection and blames you.

Do not refresh on every call. It is wasteful, it burns rate limit, and on rotating platforms it multiplies the number of chances to drop a token.

The race nobody plans for

Two workers refresh the same token at the same time. On a rotating platform, the first call invalidates the refresh token that the second call is holding. The second call fails, and depending on your error handling it may overwrite good state with the result of a failed refresh.

The fix is a lock keyed on the account, not on the worker:

const lockKey = `oauth:refresh:${socialAccountId}`;
const acquired = await redis.set(lockKey, workerId, "NX", "EX", 30);

if (!acquired) {
  // Someone else is refreshing. Wait for their result rather than
  // starting a second refresh that will invalidate theirs.
  await waitForUnlock(lockKey, { timeoutMs: 30_000 });
  return loadAccount(socialAccountId);
}

try {
  return await refreshAndPersist(socialAccountId);
} finally {
  await redis.del(lockKey);
}

The waiting branch matters as much as the locking branch. A worker that gives up and proceeds with a stale token produces the same failure the lock was meant to prevent.

This becomes non-optional at fleet scale. With a few hundred accounts you might never see it. With tens of thousands of connected accounts and a refresh job running continuously, a collision that happens one time in ten thousand happens several times a day.

The shorter path

Three expiry models, four distinct ways to lose access that all look identical from the outside, rotation semantics that differ per platform, and a locking problem that only appears under load. None of it is conceptually hard. All of it is load-bearing, and it fails quietly.

bundle.social's OAuth API handles the connection flow, the refresh schedule, the rotation writes and the locking for every platform we support. You get a webhook when an account genuinely needs the user's attention, with the reason attached, so your UI can say "ask an admin to re-add you to this Page" instead of "reconnect".

Frequently asked questions

How long do social media API access tokens last?

From 24 hours on TikTok to effectively forever on X with offline.access. Meta platforms sit around 60 days, Google-based platforms at 1 hour with a long-lived refresh token. There is no common default, so token lifetime has to be stored per account rather than assumed.

What happens if I lose the refresh token?

On rotating platforms, the account is unrecoverable without the user authorising again. On non-rotating platforms you can usually keep using the one you have. This is why the refresh token write must be atomic with the access token write.

Which social platforms rotate the refresh token?

TikTok, X, Discord and Bluesky, in our experience. Google, Pinterest and Reddit do not rotate under normal conditions. Meta platforms have no separate refresh token at all - the long-lived exchange they use instead is in Meta's access token guide.

Why does my token stop working before it expires?

Four common reasons: the user changed their password, they revoked the app, they lost their admin role on the Page or organisation, or the platform flagged the account and requires a checkpoint. Only the first two are fixed by reconnecting.

Should I refresh tokens on a schedule or before each call?

Both. A background job at roughly a quarter of the token's lifetime, plus a reactive check before publishing as a safety net. Refreshing on every call wastes rate limit and, on rotating platforms, increases the chance of dropping a token.

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.