Nothing Works Until Meta Says So: Instagram API Setup

I built a small CLI that posts images to Instagram from a folder on my Mac. Writing the script took an evening. Getting permission to call the API took longer, and it’s the part every tutorial waves through, so here it is in full.

The goal is two values: an IG_ACCESS_TOKEN (a long-lived Instagram API token) and an IG_USER_ID (your account’s numeric ID). Everything below is the path to those.

Your account must be Professional

Personal Instagram accounts can’t publish through the API at all. Instagram app, Settings, Account type and tools, Switch to professional account. Business or Creator, both work.

You need a Meta developer app

Even though you’re not shipping an app. Go to developers.facebook.com, My Apps, Create App, use case Other, type Business.

The Facebook account that owns this app does not need to be linked to your Instagram account or share an email with it. It just owns the app. The Instagram account gets connected separately by logging in with its own credentials. That confused me for an hour.

You have to invite yourself, then accept

The app sits in Development Mode, and a Development Mode app can only touch accounts that hold a role on it. So you add your own Instagram account as an Instagram Tester under App roles, Roles. Then comes the step that’s easy to miss: go into Instagram itself, Edit Profile, Apps and Websites, Tester Invites, and accept. Until you accept, the account shows as pending on the dashboard and token generation doesn’t work.

The upside of this arrangement is that the app can stay in Development Mode forever. It only ever touches my own account, so it never needs App Review.

Two auth paths, and they don’t mix

Nothing on the dashboard tells you these are different products:

  • Instagram API with Instagram Login. Add the Instagram product to your app, choose “API setup with Instagram business login”, add your account, grant instagram_business_basic and instagram_business_content_publish, generate the token. No Facebook Page required. Host is graph.instagram.com.
  • Facebook Login for Business. The older path. Requires a Facebook Page actually linked to your Instagram account, and you dig the user ID out via GET /me/accounts, then GET /{page-id}?fields=instagram_business_account. Host is graph.facebook.com.

Most tutorials cover the older Facebook Login path, so that’s what you’ll find when you search. I went with Instagram Login and it was less hassle: no Facebook Page needed, and getting the user ID is a single request.

Generating the access token

On the app dashboard, open the Instagram product and find API setup with Instagram business login. Under “Generate access tokens”, click Add account and log in with your Instagram account (this only works after the tester invite is accepted). Grant at least these two permissions:

  • instagram_business_basic
  • instagram_business_content_publish

Then click Generate token next to your account and copy it. This is the long-lived token, and it’s your IG_ACCESS_TOKEN. You don’t have to build an OAuth flow for a personal script. The dashboard button is the whole thing.

Finding your user ID

It’s not shown in the dashboard. You ask for it:

curl "https://graph.instagram.com/v23.0/me?fields=user_id,username&access_token=YOUR_TOKEN"

The response contains two IDs. user_id is the professional-account ID that every publishing call is scoped to. id is app-scoped and will fail with a permissions error that doesn’t mention IDs at all. Copy the user_id.

The token expires in about 60 days

There’s a refresh endpoint, a plain GET that hands back a fresh 60-day token:

curl "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=YOUR_CURRENT_TOKEN"

Right now I paste the result into .env by hand, which is a time bomb I’ve scheduled for myself roughly every two months.

One last trap: location IDs

A location tag on a post is a Facebook Page ID, and looking one up through the Pages Search API needs a Facebook Login token, not the Instagram one you just spent an hour getting. I gave up and pulled the numeric ID straight out of a Facebook place page URL.


With the token and user ID in hand, actually publishing is its own story: containers, polling, image constraints, and a Node bug that had nothing to do with Instagram. That’s the next post.