API GuidesAugust 2, 202611 min readingMarcel Czuryszkiewicz

Facebook Page Access Token: Getting One That Survives

A Page token derived from a long-lived user token carries no expiry of its own, but it still dies when the user changes their password or loses their role on the Page. This guide covers the four token types, the exchange flow, debug_token verification, and system user tokens for multi-tenant apps.

Facebook Page Access Token A Facebook Page access token authorises your app to read and publish as a Page. You get one by exchanging a short-lived user token for a long-lived one, then calling /me/accounts. A Page token derived that way carries no expiry of its own - but it dies the moment the underlying user token is revoked, the user changes their password, or someone removes that person's role on the Page. "Permanent" is conditional, and this guide covers the conditions.

The four token types

four types of tokens side by side: user short/long, Page, system user. For everyone: life time, where do you get it, what is it for.

Most token problems are really "wrong token type" problems.

TokenHow you get itLifetimeWhat it's for
User (short-lived)Login dialog or Graph API Explorer1–2 hoursExchanging for a long-lived token. Nothing else
User (long-lived)fb_exchange_token grant~60 daysFetching Page tokens
PageGET /me/accountsNo expiry of its own, inherits the user token's fateReading and publishing as a Page
System userBusiness PortfolioDoes not expireServer-to-server, multi-tenant production

Two rules follow from this table and save most of the debugging:

Never publish with a user token. It works in the Graph API Explorer, which is exactly why people ship it. Then it expires in an hour.

A Page token is only as durable as the user behind it. There is no such thing as a Page token that is independent of a person - unless you use a system user, which we get to below.

Getting a long-lived Page token.

Four steps. The fourth is the one everyone skips and then regrets.

1. Get a short-lived user token

Through your login flow, or from the Graph API Explorer for testing. Request the permissions you need up front:

pages_show_list
pages_read_engagement
pages_manage_posts

2. Exchange it for a long-lived user token

curl -G "https://graph.facebook.com/v26.0/oauth/access_token" \
  -d "grant_type=fb_exchange_token" \
  -d "client_id=${APP_ID}" \
  -d "client_secret=${APP_SECRET}" \
  -d "fb_exchange_token=${SHORT_LIVED_TOKEN}"
{
  "access_token": "EAAG...",
  "token_type": "bearer",
  "expires_in": 5183944
}

5183944 seconds is about 60 days. Note this is the user token - you are not done.

This call requires your app secret, so it must run server-side. If you are tempted to do it from the browser, that is your app secret leaking to every visitor.

3. Fetch the Page tokens

curl -G "https://graph.facebook.com/v26.0/me/accounts" \
  -d "fields=id,name,access_token,tasks" \
  -d "access_token=${LONG_LIVED_USER_TOKEN}"
{
  "data": [
    {
      "id": "1234567890",
      "name": "Acme Coffee",
      "access_token": "EAAG...",
      "tasks": ["ADVERTISE", "ANALYZE", "CREATE_CONTENT", "MESSAGING", "MODERATE", "MANAGE"]
    }
  ]
}

Read the tasks array before you store anything. If CREATE_CONTENT is missing, this token cannot publish - no matter how well-formed your request is. The person who authorised your app does not hold the right role on that Page, and the fix is in Business Manager, not in your code. Surfacing this at connection time instead of at publish time is the difference between a clear onboarding error and a support ticket three weeks later.

Because this Page token came from a long-lived user token, it does not carry its own expiry.

4. Verify it - the step everyone skips

Do not assume. Ask:

curl -G "https://graph.facebook.com/v26.0/debug_token" \
  -d "input_token=${PAGE_TOKEN}" \
  -d "access_token=${APP_ID}|${APP_SECRET}"
{
  "data": {
    "app_id": "1234567890",
    "type": "PAGE",
    "application": "Your App",
    "data_access_expires_at": 1789000000,
    "expires_at": 0,
    "is_valid": true,
    "scopes": ["pages_show_list", "pages_read_engagement", "pages_manage_posts"],
    "profile_id": "1234567890",
    "user_id": "9876543210"
  }
}

The fields that matter:

FieldWhat to check
typeMust be PAGE. If it says USER, you stored the wrong token
expires_at0 means no expiry - this is what you want
is_validSelf-explanatory, and worth asserting in a health check
scopesThe permissions actually granted, which may be fewer than you requested
data_access_expires_atThe one nobody notices. See below

Neither of the top-ranking guides on this topic explains debug_token, which is odd, because it answers "is my token actually fine?" definitively and takes one call. Pay attention to scopes in particular. Users can decline individual permissions in the login dialog without your app being told at authorisation time, so the granted set is often smaller than the set you asked for - which permissions you actually need, and which of them require App Review, is covered in Facebook API permissions and app review.

The data access expiry trap

expires_at: 0 and data_access_expires_at set to a real timestamp is a combination that confuses people. They are different clocks:

  • expires_at - when the token stops authenticating. 0 means never.
  • data_access_expires_at - when your app's permission to read user data lapses, typically 90 days after the user last engaged with your app.

A token can be valid and still return empty data because data access lapsed. The user has to re-authorise to reset that clock. If your integration goes quiet after roughly three months with no error, this is usually why.

Is there really a permanent Page token?

Yes, with conditions - and the conditions are what the popular guides get wrong.

A Page token from a long-lived user token has expires_at: 0. It is permanent in the sense that no timer kills it. But it stops working when any of these happen:

EventResultYour recovery
User changes their Facebook passwordToken invalidatedRe-authenticate the user
User revokes your app in Facebook settingsToken invalidatedRe-authenticate
User's role on the Page is removedToken rejected for that PageTell the customer who to re-add
Facebook forces a security checkpointToken invalidated until resolvedUser must clear it on facebook.com
Your app's permissions change in reviewScopes shrinkRe-request consent
90 days of no user engagementData access lapsesUser re-authorises

So "permanent" means "no scheduled expiry", not "will always work". Build for revocation, not for renewal - there is no refresh endpoint that fixes any row in that table.

Don't want to build token rotation for hundreds of accounts? That's exactly what we do

System user tokens: the real answer for multi-tenant

If you are building a SaaS that publishes on behalf of many customers, tying every Page to an individual employee's personal Facebook account is a liability. That person leaves, changes their password, or loses their Page role, and your customer's publishing stops.

System users solve this. They live in a Business Portfolio rather than being a person, and their tokens do not expire and are not attached to anyone's password.

They also change your rate-limit model, which is a real operational difference: calls made with a Page or system user token fall under Business Use Case limits - 4800 × engaged users per 24 hours - rather than the platform limit of 200 × users per hour. Meta explicitly recommends system user tokens to avoid rate-limiting problems when using Page Public Content Access.

Neither of the guides currently ranking for this keyword mentions system users at all, which makes them fine for a solo developer and actively misleading for anyone building a product.

What actually breaks in production

When a token fails, Graph API tells you exactly what happened via the subcode on error 190. Treating them all as "token expired, retry" is the most common and most expensive mistake here.

{
  "error": {
    "message": "Error validating access token",
    "type": "OAuthException",
    "code": 190,
    "error_subcode": 492,
    "fbtrace_id": "EJplcsCHuLu"
  }
}
SubcodeMeaningRetryable?What to do
458App not installedNoUser never authorised, or removed the app. Re-authenticate
459User checkpointedNoUser must log in to facebook.com and clear a security check
460Password changedNoRe-authenticate
463Token expiredNoRefresh the user token, re-fetch Page tokens
464Unconfirmed userNoUser must resolve an account issue
467Invalid access tokenNoRevoked or malformed. Re-authenticate
492Invalid sessionNoThe user lost their role on the Page. Your token is fine

None of them are retryable. Every 190 subcode is terminal. A retry loop on 190 burns your rate limit to produce the identical failure, and in aggregate it is the single most wasteful pattern we see in Graph API integrations. The full error-code table, including the transient codes that are worth retrying, is in our Facebook Graph API guide - and the budget you are burning when you retry a terminal error is explained in social media API rate limits.

492 deserves its own handling path. It does not mean anything is wrong with your token or your code. Someone removed that user's admin role on the Page in Business Manager. The only useful response is to tell the customer, by name, which Page lost access and who needs to restore it. Silently retrying leaves them with a broken integration and no explanation.

The relative frequencies are worth knowing before you decide which recovery path to build first. Measured against all Facebook errors we see in production: 460 (password changed) is the most common single subcode at about 5%, followed by 463 (token expired) and 492 (lost Page role) at roughly 2% each, with the remaining subcodes and terminal codes together accounting for about 10%.

That ordering surprises most teams. The dominant cause of a dead Page token is not expiry - the refresh flow above handles expiry well - it is a user changing their Facebook password, which invalidates every token issued to that user and gives you no warning at all. If you only build one recovery path, build the one that survives 460: detect it, mark the connection dead immediately, and prompt that specific user to reconnect. Waiting for a refresh cycle to notice is a day of silently dropped posts.

Storing tokens safely

A Page access token is a credential. Treat it like one.

  • Encrypt at rest. A leaked token publishes as your customer's brand until someone notices.
  • Never send it to the browser. Any token that reaches client-side JavaScript is compromised.
  • Use appsecret_proof on server-side calls so a stolen token cannot be used from elsewhere:
APPSECRET_PROOF=$(echo -n "${PAGE_TOKEN}" | openssl dgst -sha256 -hmac "${APP_SECRET}" | cut -d' ' -f2)

curl -G "https://graph.facebook.com/v26.0/${PAGE_ID}/feed" \
  -d "access_token=${PAGE_TOKEN}" \
  -d "appsecret_proof=${APPSECRET_PROOF}"
  • Run a daily debug_token sweep across stored tokens and flag anything where is_valid is false or data_access_expires_at is within two weeks. Catching an expiring connection before the customer's post fails is worth more than any retry logic.

The shorter path

Token exchange, Page enumeration, tasks validation, debug_token health checks, subcode classification, encrypted storage, and re-auth prompts - that is a meaningful amount of code before you publish a single post. Multiply it by Instagram, LinkedIn, TikTok, and X, each with a different token model.

bundle.social's Facebook API integration handles the whole lifecycle. Your users connect their Page through a hosted OAuth flow; we store, verify, and refresh. When a connection breaks, you get a webhook telling you which account and why, instead of discovering it through a failed post.

For the wider picture on how token lifecycles differ per platform, see OAuth token refresh across social APIs.

Facebook tokens that you don't have to manage

FAQ

How long does a Facebook Page access token last? A Page token derived from a long-lived user token has no expiry of its own - debug_token reports expires_at: 0. It stops working when the user changes their password, revokes the app, loses their Page role, or when data access lapses after about 90 days of inactivity.

How do I get a permanent Page access token? Exchange a short-lived user token for a long-lived one, then call /me/accounts. For production multi-tenant apps, use a system user token from a Business Portfolio instead - it is not tied to any individual's account.

Why does my Page token work for reading but not publishing? Check the tasks array from /me/accounts. Publishing needs CREATE_CONTENT, and the pages_manage_posts permission must be granted and approved.

What does error subcode 492 mean? The user associated with the Page token no longer holds an appropriate role on that Page. The token itself is valid. Someone must restore their access in Business Manager.

Can I refresh a Page access token? No. There is no refresh endpoint for Page tokens. You re-derive them by exchanging a fresh user token and calling /me/accounts again.

Is it safe to store Page access tokens in my database? Only encrypted at rest, server-side, and never exposed to the client. Enable appsecret_proof so a stolen token cannot be replayed from another origin.

bundle.social

Token lifecycle for fifteen platforms, refreshed without you watching it.

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

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.