Instagram API Access Token: Get, Exchange, Refresh
An Instagram API access token comes from Meta's OAuth flow. With Instagram Login you get a 1-hour token, exchange it for a 60-day long-lived token on graph.instagram.com, and refresh it before day 60. With Facebook Login you publish with a Page access token. Adeli holds the Meta app and refreshes every connected account's token for you.
An Instagram API access token is the credential Meta issues when an Instagram professional account authorizes your app, and every call to the Instagram API needs one. How you get it depends on the login. With Instagram Login, you trade an authorization code for a 1-hour token, exchange that for a 60-day token, and refresh it before it lapses. With Facebook Login, you publish with a Page access token taken from a long-lived Facebook User token.
Below are both flows with the exact endpoints, then a refresh schedule and the errors you hit when a token dies.
What is an Instagram API access token?
It's a string that authorizes your app to act for one Instagram account. Each connected account has its own token, and that token only carries the permissions the user granted. There's no app-wide Instagram key: your Meta app has an App ID and App Secret, and the tokens come from each user's login.
Tokens are scoped to a login type. A token from Instagram Login works on graph.instagram.com. A token from Facebook Login works on graph.facebook.com. Send a token to the other host and the request fails, so check the host first when a token that worked in testing breaks in production.
Instagram Login vs. Facebook Login tokens
Pick the column that matches how your users connect. Instagram Login needs no Facebook Page. Facebook Login needs the Instagram account linked to a Page the user manages.
| Instagram Login | Facebook Login | |
|---|---|---|
| Token you call the API with | Instagram User access token | Facebook Page access token (for publishing) |
| API host | graph.instagram.com |
graph.facebook.com |
| First token | Short-lived, 1 hour | Short-lived Facebook User token |
| Long-lived token | 60 days, via ig_exchange_token |
User token about 60 days, via fb_exchange_token |
| Refresh | ig_refresh_token, adds 60 days |
Long-lived Page token has no expiration date |
| Base permission | instagram_business_basic |
instagram_basic plus Page permissions |
| Facebook Page needed | No | Yes |
Sources: Meta's Instagram Platform overview, Business Login for Instagram, and long-lived tokens for Facebook Login.
Both logins serve professional accounts only. Meta says app users "must have an Instagram professional account", meaning Business or Creator. The two logins use different scopes, and our Instagram API permissions guide lists every one.
How to get a short-lived token
With Instagram Login, send the user to authorize, catch the code, and POST it back. The code is valid for 1 hour and can be used once, per Meta's Business Login docs.
- Send the user to the authorization URL. Use
https://www.instagram.com/oauth/authorizewithclient_id,redirect_uri,response_type=code, andscope. Addstateto guard against CSRF. - Read the code from your redirect URI. Meta appends
#_to the end of the redirect. It isn't part of the code, so strip it. - Exchange the code for a token. POST it to
api.instagram.comfrom your server:
curl -X POST "https://api.instagram.com/oauth/access_token" \
-F "client_id=<APP_ID>" \
-F "client_secret=<APP_SECRET>" \
-F "grant_type=authorization_code" \
-F "redirect_uri=<REDIRECT_URI>" \
-F "code=<AUTH_CODE>"The response holds access_token, user_id, and permissions. That token is short-lived and valid for 1 hour, according to Meta's get started guide. A reused or malformed code returns OAuthException with the message "Matching code was not found or was already used".
For a test on your own account, you can skip the flow and generate a token in the App Dashboard on the Instagram API setup page. Meta says dashboard tokens are long-lived, valid for 60 days.
With Facebook Login, you run the standard Facebook Login flow instead and get a short-lived Facebook User token. The next section turns it into a Page token.
How to exchange it for a long-lived token
Call the exchange endpoint from your server within the hour. It takes your App Secret, so Meta says it must run in server-side code, never client-side.
Instagram Login. A GET to graph.instagram.com/access_token with grant_type=ig_exchange_token (reference):
curl -G "https://graph.instagram.com/access_token" \
--data-urlencode "grant_type=ig_exchange_token" \
--data-urlencode "client_secret=<APP_SECRET>" \
--data-urlencode "access_token=<SHORT_LIVED_TOKEN>"You get back access_token, token_type (bearer), and expires_in in seconds. The new token lasts 60 days. Store expires_in with it, because your refresh job needs the date.
Facebook Login. A GET to oauth/access_token with grant_type=fb_exchange_token, your App ID, App Secret, and the short-lived User token (guide):
curl -G "https://graph.facebook.com/oauth/access_token" \
--data-urlencode "grant_type=fb_exchange_token" \
--data-urlencode "client_id=<APP_ID>" \
--data-urlencode "client_secret=<APP_SECRET>" \
--data-urlencode "fb_exchange_token=<SHORT_LIVED_USER_TOKEN>"That long-lived User token generally lasts about 60 days. Then call GET /<APP_SCOPED_USER_ID>/accounts with it, and each Page in the data array comes with its own access_token. Page tokens generated from a long-lived User token have no expiration date, so that's the token to store for publishing, which Meta's content publishing docs say needs a Page access token.
How to refresh an Instagram access token
Call refresh_access_token before day 60, and each refresh resets the clock to 60 days. This applies to Instagram Login tokens. Meta's refresh reference sets three conditions:
- At least 24 hours old. A token issued today can't be refreshed until tomorrow.
- Not expired. An expired token can't be refreshed at all.
instagram_business_basicgranted. The user must have approved the base permission.
curl -G "https://graph.instagram.com/refresh_access_token" \
--data-urlencode "grant_type=ig_refresh_token" \
--data-urlencode "access_token=<LONG_LIVED_TOKEN>"The response is a new access_token with a fresh expires_in. Save it over the old one. Tokens that go 60 days without a refresh expire and can't be refreshed, per the Business Login docs, so the user has to log in again.
A refresh schedule that keeps tokens alive:
| When | What your job does |
|---|---|
| Day 0 | Store the token and its expiry from expires_in |
| Day 1 or later | Token becomes eligible for refresh |
| Around day 45 | Refresh, and store the new token and expiry |
| Refresh fails | Retry, then flag the account for reconnection before day 60 |
| Error 190 at any point | Mark the account disconnected and ask the user to log in again |
Refreshing two weeks early leaves room for retries when a job fails or the queue backs up. With hundreds of connected accounts, run it daily over every account whose expiry falls inside that window.
How to check if a token is valid
For Instagram Login, call /me. For Facebook Login, call debug_token.
Instagram Login. A successful call to /me with fields=user_id,username shows the token works and returns the account's ID and username (get started guide):
curl -G "https://graph.instagram.com/v25.0/me" \
--data-urlencode "fields=user_id,username" \
--data-urlencode "access_token=<ACCESS_TOKEN>"Facebook Login. The debug_token endpoint returns metadata about a token: is_valid, expires_at, data_access_expires_at, and scopes. You call it with an app access token or an app developer's user token for the same app:
curl -G "https://graph.facebook.com/debug_token" \
--data-urlencode "input_token=<TOKEN_TO_CHECK>" \
--data-urlencode "access_token=<APP_ACCESS_TOKEN>"Meta's debug_token reference doesn't mention Instagram Login tokens, so don't build your Instagram Login checks on it.
Why did my Instagram token stop working?
Check the error code first. It usually names the cause, and these codes come from Meta's Graph API error handling codes.
| Symptom | Likely cause | Fix |
|---|---|---|
| Error 190, subcode 463 or 467 | Token expired, was revoked, or is invalid | Send the user back through login |
| Error 190, subcode 460 | The user changed their password (Facebook Login) | Have the user log in to your app again |
| Error 190, subcode 458 | The user removed your app | Reauthenticate the user |
| Error 10 or 200–299 | A permission was never granted or was removed | Request the missing scope again |
| Calls fail for other people's accounts | Your app only has Standard Access | Get Advanced Access through App Review |
A few more catches:
- Personal accounts. Neither login supports them. The user has to switch to a Business or Creator account first.
- Standard Access. Meta's overview says Standard Access is for people with a role on your app. Accounts you don't own or manage need Advanced Access, App Review, and Business Verification. Our Meta app review guide covers the timeline.
- Old scope names. Meta deprecated the
business_*scopes on January 27, 2025. Apps still requesting them can't call the Instagram endpoints until they switch to theinstagram_business_*names. - Wrong host. An Instagram User token sent to
graph.facebook.com, or a Page token sent tograph.instagram.com, fails. - Leaked tokens. Keep tokens and your App Secret out of client code, URLs you log, and public repos. Treat a leaked token as compromised and reconnect the account.
Is there a faster way to manage Instagram tokens?
Yes, by letting a unified API hold them. Adeli holds the Meta developer app, carried Meta's review, and refreshes every connected account's tokens. You hold one API key and send it as a bearer header, as the authentication docs describe. There's no refresh job to run and no 60-day clock to watch.
Each of your users connects their own Instagram account, and Adeli's posting API publishes feed posts, Reels, Stories, and carousels from the same endpoint as every other network. You get a per-platform status on every publish and a webhook when anything changes. Your first 3 connected accounts are free, and there are no per-post fees on Instagram.