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.
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

Most token problems are really "wrong token type" problems.
| Token | How you get it | Lifetime | What it's for |
|---|---|---|---|
| User (short-lived) | Login dialog or Graph API Explorer | 1–2 hours | Exchanging for a long-lived token. Nothing else |
| User (long-lived) | fb_exchange_token grant | ~60 days | Fetching Page tokens |
| Page | GET /me/accounts | No expiry of its own, inherits the user token's fate | Reading and publishing as a Page |
| System user | Business Portfolio | Does not expire | Server-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:
| Field | What to check |
|---|---|
type | Must be PAGE. If it says USER, you stored the wrong token |
expires_at | 0 means no expiry - this is what you want |
is_valid | Self-explanatory, and worth asserting in a health check |
scopes | The permissions actually granted, which may be fewer than you requested |
data_access_expires_at | The 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.0means 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:
| Event | Result | Your recovery |
|---|---|---|
| User changes their Facebook password | Token invalidated | Re-authenticate the user |
| User revokes your app in Facebook settings | Token invalidated | Re-authenticate |
| User's role on the Page is removed | Token rejected for that Page | Tell the customer who to re-add |
| Facebook forces a security checkpoint | Token invalidated until resolved | User must clear it on facebook.com |
| Your app's permissions change in review | Scopes shrink | Re-request consent |
| 90 days of no user engagement | Data access lapses | User 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" } }
| Subcode | Meaning | Retryable? | What to do |
|---|---|---|---|
458 | App not installed | No | User never authorised, or removed the app. Re-authenticate |
459 | User checkpointed | No | User must log in to facebook.com and clear a security check |
460 | Password changed | No | Re-authenticate |
463 | Token expired | No | Refresh the user token, re-fetch Page tokens |
464 | Unconfirmed user | No | User must resolve an account issue |
467 | Invalid access token | No | Revoked or malformed. Re-authenticate |
492 | Invalid session | No | The 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_tokensweep across stored tokens and flag anything whereis_validis false ordata_access_expires_atis 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.