API and developers

How to Post to Instagram API in 2026

The Instagram Graph API has a lot of moving parts. This tutorial walks through every step (authentication, media containers, publishing) with working code in Node.js and Python.

Robert LigthartFounder of OmniSocialsPublished Updated 12 min read
How to Post to Instagram API in 2026

If you've ever tried to automate Instagram posts for a client dashboard, a content tool, or your own app, you know the Instagram Graph API is not exactly beginner-friendly. The setup involves multiple Facebook products, OAuth flows, and a two-step publish process that trips up most developers the first time.

On this page
  1. What You Need Before You Start
  2. Step 1: Create Your Facebook Developer App
  3. Step 2: Get Your Instagram User ID and Access Token
  4. How does Instagram API authentication work?
  5. Step 3: Create a Media Container
  6. Step 4: Publish the Media Container
  7. Posting a Reel via the API
  8. Error Handling
  9. A Simpler Option: OmniSocials API
  10. How to Use the Instagram API with Python
  11. Common Pitfalls
  12. Frequently Asked Questions

This tutorial covers the complete flow: setting up a Facebook Developer App, getting an access token, uploading media, and publishing a post. I'll show working code in both Node.js and Python. I'll also cover the most common errors and how to avoid them.

If you want to skip the direct API complexity entirely, I'll show how OmniSocials API wraps all of this into a single authenticated call at the end. For a broader comparison of all major social platform APIs, see our social media API guide for developers. If you're looking for a no-code scheduling solution instead, our Instagram scheduling tool comparison covers the best options.


What You Need Before You Start

Before writing a single line of code, you need three things in place:

  • An Instagram Business or Creator account. Personal accounts cannot use the publishing API.
  • A Facebook Page linked to that Instagram account. This is required for API access, even if you never use the Page itself.
  • A Facebook Developer account and App. Free to create at developers.facebook.com.

The Instagram Graph API does not work with personal Instagram accounts. If you're building for a client, they need to convert their account in Instagram Settings > Account > Switch to Professional Account.


Step 1: Create Your Facebook Developer App

Go to developers.facebook.com and click Create App. Select Business as the app type.

Once the app is created, navigate to Add Products in the left sidebar and add Instagram Graph API. This gives you access to the publishing endpoints.

You'll also need to add these permissions to your app:

  • instagram_basic
  • instagram_content_publish
  • pages_read_engagement

For testing with your own account, you can work in Development mode. For publishing on behalf of other users, you need to go through Meta's App Review process.


Step 2: Get Your Instagram User ID and Access Token

How does Instagram API authentication work?

Instagram Graph API authentication uses OAuth 2.0 via Meta's login flow. You get a short-lived user access token (valid 1 hour), exchange it for a long-lived token (valid 60 days), then use that token with your Instagram Business Account ID to make publishing calls. You also need to refresh the long-lived token before it expires.

Getting your access token involves a few steps. The fastest way for testing is through the Graph API Explorer.

  1. Select your app from the dropdown.
  2. Select User Token and check the permissions: instagram_basic, instagram_content_publish, pages_read_engagement.
  3. Click Generate Access Token and authorize.

This gives you a short-lived token. Exchange it for a long-lived one:

curl -i -X GET "https://graph.facebook.com/oauth/access_token
  ?grant_type=fb_exchange_token
  &client_id={app-id}
  &client_secret={app-secret}
  &fb_exchange_token={short-lived-token}"

Store this long-lived token securely. Never expose it in client-side code.

Next, get your Instagram Business Account ID:

curl -i -X GET "https://graph.facebook.com/v19.0/me/accounts?access_token={long-lived-token}"

This returns your connected Pages. Take the Page ID and use it to get the Instagram account linked to it:

curl -i -X GET "https://graph.facebook.com/v19.0/{page-id}?fields=instagram_business_account&access_token={token}"

The instagram_business_account.id value is what you'll use in every subsequent API call.


Step 3: Create a Media Container

Instagram publishing is a two-step process. You don't post directly. First you create a media container (a staging object), then you publish it.

For an image post, the image must be publicly accessible via HTTPS. Instagram's servers fetch the image directly, so a localhost URL won't work.

Node.js example:

const axios = require('axios');

const IG_USER_ID = 'your_instagram_user_id';
const ACCESS_TOKEN = 'your_long_lived_token';
const IMAGE_URL = 'https://yourdomain.com/path/to/image.jpg';
const CAPTION = 'Posted via API in 2026. #automation';

async function createMediaContainer() {
  const response = await axios.post(
    `https://graph.facebook.com/v19.0/${IG_USER_ID}/media`,
    {
      image_url: IMAGE_URL,
      caption: CAPTION,
      access_token: ACCESS_TOKEN,
    }
  );

  return response.data.id; // This is your container ID
}

createMediaContainer().then(id => console.log('Container ID:', id));

Python example:

import requests

IG_USER_ID = "your_instagram_user_id"
ACCESS_TOKEN = "your_long_lived_token"
IMAGE_URL = "https://yourdomain.com/path/to/image.jpg"
CAPTION = "Posted via API in 2026. #automation"

def create_media_container():
    url = f"https://graph.facebook.com/v19.0/{IG_USER_ID}/media"
    payload = {
        "image_url": IMAGE_URL,
        "caption": CAPTION,
        "access_token": ACCESS_TOKEN,
    }
    response = requests.post(url, data=payload)
    response.raise_for_status()
    return response.json()["id"]

container_id = create_media_container()
print(f"Container ID: {container_id}")

A successful response looks like:

{ "id": "17889615814797777" }

Step 4: Publish the Media Container

Once you have a container ID, you publish it with a second API call.

Node.js:

async function publishMedia(containerId) {
  const response = await axios.post(
    `https://graph.facebook.com/v19.0/${IG_USER_ID}/media_publish`,
    {
      creation_id: containerId,
      access_token: ACCESS_TOKEN,
    }
  );

  return response.data.id; // The published post ID
}

createMediaContainer()
  .then(publishMedia)
  .then(postId => console.log('Published post ID:', postId))
  .catch(err => console.error('Error:', err.response?.data || err.message));

Python:

def publish_media(container_id):
    url = f"https://graph.facebook.com/v19.0/{IG_USER_ID}/media_publish"
    payload = {
        "creation_id": container_id,
        "access_token": ACCESS_TOKEN,
    }
    response = requests.post(url, data=payload)
    response.raise_for_status()
    return response.json()["id"]

container_id = create_media_container()
post_id = publish_media(container_id)
print(f"Published post ID: {post_id}")

A successful response:

{ "id": "17855590848109401" }

That's your live Instagram post ID. You can use it to fetch insights or track engagement later.


Posting a Reel via the API

Reels use the same two-step flow, but with media_type=REELS and a video_url instead of image_url.

async function createReelContainer() {
  const response = await axios.post(
    `https://graph.facebook.com/v19.0/${IG_USER_ID}/media`,
    {
      media_type: 'REELS',
      video_url: 'https://yourdomain.com/path/to/video.mp4',
      caption: 'A Reel posted via the Instagram API #automation',
      access_token: ACCESS_TOKEN,
    }
  );
  return response.data.id;
}

For videos, there's an extra step: you need to check the container's status_code before publishing. Video processing takes time, and publishing before it's ready returns an error.

async function waitForVideoReady(containerId) {
  const maxAttempts = 10;
  for (let i = 0; i < maxAttempts; i++) {
    const res = await axios.get(
      `https://graph.facebook.com/v19.0/${containerId}`,
      { params: { fields: 'status_code', access_token: ACCESS_TOKEN } }
    );

    if (res.data.status_code === 'FINISHED') return true;
    if (res.data.status_code === 'ERROR') throw new Error('Video processing failed');

    await new Promise(r => setTimeout(r, 5000)); // wait 5 seconds between checks
  }
  throw new Error('Timed out waiting for video processing');
}

Always poll for FINISHED before calling media_publish on video content.


Error Handling

The Instagram Graph API returns errors in a consistent structure. Here's what to watch for:

{
  "error": {
    "message": "Invalid OAuth access token.",
    "type": "OAuthException",
    "code": 190,
    "fbtrace_id": "AbCdEfGhIjK"
  }
}

Common error codes:

CodeMeaningFix
Code: 190Meaning: Invalid or expired tokenFix: Refresh your long-lived token
Code: 100Meaning: Invalid parameterFix: Check image_url is publicly accessible HTTPS
Code: 24Meaning: This media container has already been publishedFix: Don't reuse container IDs
Code: 9007Meaning: User is not an Instagram Business or CreatorFix: Account type needs to change
Code: 32Meaning: Page request limit reachedFix: You've hit 25 posts/24h

Add error handling that logs the full error object, not just the status code:

try {
  const postId = await publishMedia(containerId);
  console.log('Success:', postId);
} catch (err) {
  const apiError = err.response?.data?.error;
  if (apiError) {
    console.error(`API Error ${apiError.code}: ${apiError.message}`);
  } else {
    console.error('Unexpected error:', err.message);
  }
}

A Simpler Option: OmniSocials API

The two-step container flow, token management, video polling, and permission setup add real overhead to any project. OmniSocials API wraps all of this into a single authenticated POST request.

You create one API key in your OmniSocials workspace and connect Instagram once in the dashboard. After that, posting to Instagram with the official Node.js SDK (npm install @omnisocials/sdk) looks like this:

import { OmniSocials } from '@omnisocials/sdk';

const client = new OmniSocials(); // reads OMNISOCIALS_API_KEY

const { data: post } = await client.posts.create({
  content: 'Posted via the OmniSocials API #automation',
  channels: ['instagram'],
  media_urls: ['https://yourdomain.com/image.jpg'],
  scheduled_at: '2027-03-02T10:00:00Z', // leave out to save a draft
});

console.log(post.id, post.status); // "1019" "scheduled"

For a Reel, add type: 'reel' and pass a video URL. For a carousel, pass up to 10 URLs in media_urls. The same call works for Facebook, LinkedIn, TikTok, and 8 other platforms: add them to channels. No container IDs, no separate publish step, no video polling. OmniSocials handles token refresh automatically so your integration doesn't break when a 60-day token expires. You can find the full reference at docs.omnisocials.com.

The trade-off: you're routing through OmniSocials rather than Meta's API directly. For most automation use cases, that's a worthwhile simplification. If you need direct access to raw Graph API data like story replies or live video metrics, the direct API is still the right choice. For the TikTok equivalent of this guide, see how to post to TikTok via the API.


How to Use the Instagram API with Python

The Python examples above call the Graph API with requests: create a container, wait for the video, then publish. If you'd rather skip those steps, the official OmniSocials Python SDK posts to Instagram in one call.

Install it with pip install omnisocials (Python 3.9 or newer), connect Instagram once in the OmniSocials dashboard, and store your API key as OMNISOCIALS_API_KEY. This script schedules a Reel:

from omnisocials import OmniSocials, ValidationError

client = OmniSocials()  # reads OMNISOCIALS_API_KEY

try:
    reel = client.posts.create(
        content="3 editing tricks that cut our video time in half #reels",
        channels=["instagram"],
        type="reel",
        media_urls=["https://yourdomain.com/editing-tricks.mp4"],
        instagram={"share_to_feed": True},
        scheduled_at="2027-03-02T10:00:00Z",
    )
    print(reel["data"]["id"], reel["data"]["status"])  # 1019 scheduled
except ValidationError as err:
    print(err.code, err.message)

Instagram always needs media, so a post without an image or video is rejected with a 400. A Reel must be a video (MP4 or MOV). A few variations:

  • Image post: leave out type and pass an image URL in media_urls.
  • Carousel: pass up to 10 URLs. Images and videos can be mixed.
  • Bigger files: media_urls takes files up to 100 MB. For a larger video, upload it first with client.media.upload_from_url(url=...) (up to 1 GB) and pass the returned id in media_ids.
  • Publish now: leave out scheduled_at to save a draft, or call client.posts.create_and_publish() with the same arguments to post right away.

OmniSocials handles the container, the video processing check and the publish step for you.


Common Pitfalls

The image URL is not publicly accessible. This is the most common failure on first attempt. Instagram's servers fetch your image. If it's behind auth, on localhost, or returning a redirect, the container creation fails with a vague error. Host the image on S3, Cloudflare R2, or any public CDN first.

Publishing a video before it's done processing. Calling media_publish immediately after creating a video container almost always returns an error. Always poll for status_code === 'FINISHED' first.

Reusing a container ID. Each container can only be published once. If your publish call fails and you retry, you need to create a new container. Storing and reusing the same container ID is error code 24.

Token expiry in production. Long-lived tokens last 60 days. Build a token refresh job into your system from the start. A token that expires at 3am on a Saturday has broken more than a few production integrations.

App in Development mode. While in Development mode, your app can only interact with accounts that are added as testers or developers in the App settings. If a user outside your app tries to connect, it silently fails.


Frequently Asked Questions

Can I post to Instagram API without a business account?

No. The Instagram Graph API only supports Instagram Business and Creator accounts. Personal accounts cannot use the publishing API. You also need the account linked to a Facebook Page before you can authenticate and post programmatically.

Does the Instagram API support posting Reels?

Yes. The Instagram Graph API supports Reels via the REELS media type. You upload a publicly accessible video URL, set media_type=REELS, and publish it via the /media_publish endpoint. The video must be an MP4 between 3 seconds and 15 minutes.

How often do Instagram API access tokens expire?

Short-lived tokens expire after 1 hour. Long-lived user tokens last 60 days. You should refresh your long-lived token before it expires by calling the token refresh endpoint. System user tokens from the Meta Business Suite can be set to never expire.

What is the Instagram API rate limit for publishing?

As of 2026, the Instagram Graph API allows 50 API calls per user per hour for most endpoints. For media publishing specifically, you are limited to 25 posts per 24 hours per Instagram account, which matches the manual posting limit.

Can I post carousels via the Instagram API?

Yes. Carousel posts require creating individual item containers first, then creating a parent carousel container that references all the item IDs, and finally publishing the parent container. You can include 2 to 10 images or videos in one carousel.

How do I use the Instagram API with Python?

You can call the Instagram Graph API with requests: create a media container, poll video containers until they're FINISHED, then publish with /media_publish. Or install the OmniSocials Python SDK with pip install omnisocials and call client.posts.create() with channels=["instagram"] and a media_urls list. Add type="reel" for a Reel. One call covers the container and publish steps.

Written byRobert LigthartFounder of OmniSocials

Try OmniSocials for 14 days

Plan, schedule and answer on 13 platforms from one calendar, or let your AI agent do it. The first charge comes when the trial ends.