How to Get an Instagram API Access Token, Step by Step
How to get an Instagram access token in 2026: why there is no Instagram API key, the App Dashboard shortcut for your own account, the full Business Login flow with Node and Python, the 60-day long-lived token, and how to refresh it.
TLDR: Instagram has no API key. Every request carries an OAuth access token for one professional account. For an account you own, the App Dashboard generates a 60-day token in two clicks. For anyone else’s, Business Login returns a one-hour token that you exchange for a 60-day one and refresh before it expires. Checked against Meta’s docs on September 28, 2026.
Is there an Instagram API key?
No, and the search phrase is the first thing to unlearn. An API key is a single secret that identifies your app and authorizes every call. Instagram’s API does not work that way. It uses OAuth 2.0, so there are three separate things:
- An Instagram app ID and app secret, which identify your Meta app. They are shown in the App Dashboard and never sent on ordinary API calls.
- An authorization code, which Instagram hands back after a user approves your app. It lives for one hour and can be used once.
- An access token, which you attach to every API request. It is scoped to one Instagram professional account and one app, and it carries the permissions that account granted.
So “get an Instagram API key” really means “get an access token for a specific account”. There are two ways to do that, and which one you need depends on whose account it is.
Which Instagram API and which login do you need?
Meta runs two configurations of the Instagram API, and the token flow differs between them:
| Instagram API with Instagram Login | Instagram API with Facebook Login | |
|---|---|---|
| The user logs in with | Instagram credentials | Facebook credentials |
| Facebook Page required | No | Yes, linked to the Instagram account |
| API host | graph.instagram.com |
graph.facebook.com |
| Token type | Instagram User access token | Facebook User or Page access token |
| Permissions | instagram_business_basic, instagram_business_content_publish, instagram_business_manage_comments, instagram_business_manage_messages |
instagram_basic, instagram_content_publish, instagram_manage_comments, instagram_manage_insights, instagram_manage_messages, plus Page permissions |
| Missing features | Hashtag search, product tagging, partnership ads | Nothing on the publishing side |
This guide follows Instagram Login, which Meta recommends for new integrations and which needs no Facebook Page. If you already have a Page-linked setup, the Facebook Login variant at the end covers the differences. Either way the account must be an Instagram professional account, business or creator; personal accounts cannot grant tokens at all.
One more distinction that decides how much work is ahead. Standard Access, which every app has by default, covers accounts owned by people with a role on the app. Advanced Access, which requires Business Verification and App Review, is needed to get tokens from anyone else. If you only want a token for your own account, you can skip the review entirely, and the next two sections are all you need.
How do you create the Meta app?
Every token starts with an app in the Meta App Dashboard:
- Register as a Meta developer at developers.facebook.com if you have not.
- Create an app and choose the Business type. An app of any other type cannot add the Instagram product, so if you have an older app of the wrong type, create a new one.
- Add the Instagram product, then open API setup with Instagram business login in the left menu.
- Under Set up Instagram business login, note the Instagram App ID and Instagram App Secret. These are different from the Meta app ID shown at the top of the dashboard; the Instagram Login flow uses the Instagram ones.
- Add your OAuth redirect URIs. The URI you send during login has to match one of these exactly, and the dashboard sometimes appends a trailing slash, so check the saved value.
- Copy the Embed URL the dashboard builds for you, or construct the authorization URL yourself as shown below.
Keep the app secret on the server. Meta’s docs say it plainly: never put it in client-side code, a mobile binary, or a device.
How do you get an access token for your own Instagram account?
If the account belongs to you or someone with a role on your app, the App Dashboard skips the OAuth flow:
- In the app dashboard, open Instagram, then API setup with Instagram business login.
- Add the Instagram professional account under the accounts section.
- Click Generate token next to the account and log in to Instagram when prompted.
- Copy the token.
Tokens generated this way are long-lived and valid for 60 days, unlike the one-hour tokens the login flow returns. They work under Standard Access, so no review is involved. Confirm the token works by asking for the account’s ID and username:
curl "https://graph.instagram.com/v26.0/me" \
-d fields=user_id,username \
-d access_token="$IG_ACCESS_TOKEN" -G
# {"data":[{"user_id":"1784...","username":"yourbrand"}]}
The user_id is the Instagram professional account ID you will use in publishing calls. This is the whole flow for an internal tool that posts to your own account. Store the token, refresh it before day 60 as described below, and you are done.
How do you get an Instagram access token for other people’s accounts?
For a product that connects your users’ accounts, you implement Business Login for Instagram. It is a standard authorization-code flow with an extra exchange at the end. The four endpoints, from Meta’s Business Login page:
| Step | Endpoint | Method | Returns |
|---|---|---|---|
| 1. Authorize | https://www.instagram.com/oauth/authorize |
Browser redirect | An authorization code, valid 1 hour, single use |
| 2. Exchange the code | https://api.instagram.com/oauth/access_token |
POST | A short-lived token, valid 1 hour, plus the user’s ID and granted permissions |
| 3. Get a long-lived token | https://graph.instagram.com/access_token |
GET | A token valid 60 days |
| 4. Refresh | https://graph.instagram.com/refresh_access_token |
GET | A new token valid 60 days |
Step 1: send the user to Instagram
Build the authorization URL with your Instagram app ID, a registered redirect URI, response_type=code, the permissions you need, and a state value you generate per request to block CSRF:
https://www.instagram.com/oauth/authorize
?client_id=990602627938098
&redirect_uri=https://yourapp.com/instagram/callback
&response_type=code
&scope=instagram_business_basic,instagram_business_content_publish
&state=8f3c1d9a
Ask only for the permissions you will use. Publishing needs instagram_business_basic and instagram_business_content_publish; comments and messages each need their own permission, and each one you request is one more App Review submission later. When the user approves, Instagram redirects to your URI with ?code=... and echoes your state. Meta’s docs warn about one detail: the redirect ends with #_, which is not part of the code, so strip it. If the user cancels, the redirect carries error=access_denied instead, and your callback has to handle that without crashing.
Steps 2 and 3: exchange the code, then extend the token
Both exchanges happen on your server, because the second one carries the app secret. In Node:
const IG_APP_ID = process.env.IG_APP_ID
const IG_APP_SECRET = process.env.IG_APP_SECRET
const REDIRECT_URI = 'https://yourapp.com/instagram/callback'
export async function exchangeCodeForLongLivedToken(code) {
// Step 2: code -> short-lived token (valid 1 hour)
const form = new URLSearchParams({
client_id: IG_APP_ID,
client_secret: IG_APP_SECRET,
grant_type: 'authorization_code',
redirect_uri: REDIRECT_URI,
code: code.replace(/#_$/, ''),
})
const shortRes = await fetch('https://api.instagram.com/oauth/access_token', {
method: 'POST',
body: form,
})
if (!shortRes.ok) throw new Error(`code exchange failed: ${await shortRes.text()}`)
const { data } = await shortRes.json()
const { access_token: shortLived, user_id: igUserId, permissions } = data[0]
// Step 3: short-lived -> long-lived token (valid 60 days)
const url = new URL('https://graph.instagram.com/access_token')
url.searchParams.set('grant_type', 'ig_exchange_token')
url.searchParams.set('client_secret', IG_APP_SECRET)
url.searchParams.set('access_token', shortLived)
const longRes = await fetch(url)
if (!longRes.ok) throw new Error(`token exchange failed: ${await longRes.text()}`)
const long = await longRes.json()
return {
igUserId,
permissions: permissions.split(','),
accessToken: long.access_token,
// expires_in is seconds; ~5,184,000 = 60 days
expiresAt: new Date(Date.now() + long.expires_in * 1000),
}
}
The same two calls in Python:
import os
from datetime import datetime, timedelta, timezone
import requests
IG_APP_ID = os.environ["IG_APP_ID"]
IG_APP_SECRET = os.environ["IG_APP_SECRET"]
REDIRECT_URI = "https://yourapp.com/instagram/callback"
def exchange_code_for_long_lived_token(code: str) -> dict:
# Step 2: code -> short-lived token (valid 1 hour)
short = requests.post(
"https://api.instagram.com/oauth/access_token",
data={
"client_id": IG_APP_ID,
"client_secret": IG_APP_SECRET,
"grant_type": "authorization_code",
"redirect_uri": REDIRECT_URI,
"code": code.removesuffix("#_"),
},
timeout=30,
)
short.raise_for_status()
first = short.json()["data"][0]
# Step 3: short-lived -> long-lived token (valid 60 days)
long = requests.get(
"https://graph.instagram.com/access_token",
params={
"grant_type": "ig_exchange_token",
"client_secret": IG_APP_SECRET,
"access_token": first["access_token"],
},
timeout=30,
)
long.raise_for_status()
body = long.json()
return {
"ig_user_id": first["user_id"],
"permissions": first["permissions"].split(","),
"access_token": body["access_token"],
"expires_at": datetime.now(timezone.utc) + timedelta(seconds=body["expires_in"]),
}
Store the token, the expiry, the Instagram user ID, and the granted permissions together. The permissions list matters because a user can untick a permission on the consent screen, and a token without instagram_business_content_publish will fail at publish time with a permissions error rather than at login.
A code exchange that fails with “Matching code was not found or was already used” means the code expired, was consumed by a retried request, or the redirect URI did not match the one used to get it. Every one of those has happened to us.
How do you refresh an Instagram long-lived token?
A long-lived token dies on day 60 unless you refresh it, and Meta’s refresh rules are specific:
- The token must be at least 24 hours old.
- It must still be valid. An expired token cannot be refreshed, and the only fix is sending the user through login again.
- The user must have granted
instagram_business_basic.
The refresh returns a new token with a fresh 60-day clock. It needs no app secret:
export async function refreshLongLivedToken(accessToken) {
const url = new URL('https://graph.instagram.com/refresh_access_token')
url.searchParams.set('grant_type', 'ig_refresh_token')
url.searchParams.set('access_token', accessToken)
const res = await fetch(url)
if (!res.ok) throw new Error(`refresh failed: ${await res.text()}`)
const body = await res.json()
return {
accessToken: body.access_token,
expiresAt: new Date(Date.now() + body.expires_in * 1000),
}
}
def refresh_long_lived_token(access_token: str) -> dict:
res = requests.get(
"https://graph.instagram.com/refresh_access_token",
params={"grant_type": "ig_refresh_token", "access_token": access_token},
timeout=30,
)
res.raise_for_status()
body = res.json()
return {
"access_token": body["access_token"],
"expires_at": datetime.now(timezone.utc) + timedelta(seconds=body["expires_in"]),
}
In production this is a scheduled job, not a one-off. A pattern that has held up for us: run it daily, refresh every token that is more than a day old and within ten days of expiry, store the new token and expiry, and mark the account as needing reconnection when the refresh returns an error. Two events end a token early regardless of the calendar: the user changes their Instagram password, or they remove your app from their settings. Both surface as error code 190 on the next call, so treat 190 as “ask the user to reconnect”, not as a bug in your code.
How do you store and use the token safely?
- Send it only over HTTPS. Instagram’s docs pass the token as the
access_tokenquery parameter on every call, so make sure request URLs never land in access logs or analytics. - Encrypt tokens at rest and keep the app secret in a secrets manager, never in the repository.
- Store one token per account per app. The token is scoped to that pair, so a user with three accounts connects three times.
- Log the expiry, not the token. A token in a log file is a credential leak.
- Check what a token can do before trusting it. The permissions list from step 2 tells you which features to enable for that account; a token missing
instagram_business_content_publishshould never reach your publisher.
What about the Facebook Login variant?
If your users’ Instagram accounts are linked to Facebook Pages and you use Facebook Login for Business, the shape is the same but the hosts and lifetimes differ. The user authorizes through Facebook, you exchange the code at graph.facebook.com/oauth/access_token, and you extend the resulting user token with grant_type=fb_exchange_token to get a 60-day long-lived user token. From that you fetch the user’s Pages with GET /me/accounts, and each Page comes with a Page access token. Page tokens obtained from a long-lived user token do not expire on their own, which is why Page-based integrations rarely need a refresh job. The Instagram account ID comes from the Page’s instagram_business_account field. Our Facebook Graph API guide covers the Page side in detail.
The shorter path: let PostZen hold the tokens
If the reason you want a token is to publish, read comments, or pull insights for accounts your users own, the whole flow above becomes one request. PostZen holds the Meta app, the approved permissions, the Business Verification, and the refresh job. You ask for a connect URL, send the user to it, and get back a connected account:
curl -G "https://api.postzen.dev/v1/connect/instagram" \
-H "Authorization: Bearer $POSTZEN_API_KEY" \
-d profileId=jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e \
-d redirectUrl=https://yourapp.com/connected
# {"authUrl":"https://www.instagram.com/oauth/authorize?...","state":"..."}
The user completes Instagram Login on that URL, and PostZen stores the token, refreshes it on schedule, and fires an account webhook if a token dies so you can prompt a reconnect. From then on you publish with POST /v1/posts and an account ID, never a token. The Instagram target supports feed posts, carousels, Reels, and Stories, plus comments, DMs, and insights, all listed on the Instagram integration page and in the Instagram platform docs. The same connect flow works for the other nine platforms through one social media API.
Building it yourself makes sense when you need Meta features PostZen does not expose, or when your compliance rules forbid a third party holding user tokens. The Instagram API integration guide covers the publishing flow that comes after the token, and the Instagram API pricing guide covers what the review and the engineering cost, since the API itself is free.
Frequently asked questions
How do I get an Instagram API key?
There is no Instagram API key. Instagram authenticates every request with an OAuth access token tied to one Instagram professional account. Your Meta app has an app ID and an app secret, which identify the app, and each user grants it a token. For an account you own, the App Dashboard generates a 60-day token directly; for other people's accounts, you implement Business Login.
How do I get an Instagram access token for my own account?
Create a Meta app, add the Instagram product, and add your Instagram professional account under API setup with Instagram business login. Click Generate token next to the account, log in, and copy it. That token is long-lived, valid for 60 days, and works with Standard Access, so no App Review is needed.
How long does an Instagram access token last?
Tokens from the Business Login flow are short-lived and last one hour, as does the authorization code before it. Exchanging a short-lived token with your app secret gives a long-lived token valid for 60 days. A long-lived token can be refreshed for another 60 days once it is at least 24 hours old and still valid.
How do I get a long-lived Instagram access token?
Call GET https://graph.instagram.com/access_token with grant_type set to ig_exchange_token, your app secret, and a valid short-lived token. The response contains a token with expires_in of about 5,184,000 seconds, which is 60 days. Make this call from a server, because it carries the app secret.
How do I refresh an Instagram access token?
Call GET https://graph.instagram.com/refresh_access_token with grant_type set to ig_refresh_token and the long-lived token. The token must be at least 24 hours old, unexpired, and the user must have granted instagram_business_basic. The response is a new token valid for 60 days. A token that expires unrefreshed cannot be recovered; the user has to log in again.
Do I need App Review to get an Instagram access token?
Not for accounts you own or manage. Standard Access lets anyone with a role on your app generate tokens for their own professional accounts. To get tokens from people outside your app, you need Advanced Access, which means Business Verification and an App Review with a screencast for each permission.



