How to Use Instagram Graph API in 2026

Master Instagram Graph API integration with this complete guide. Learn OAuth 2.0, content publishing, insights, comments, webhooks, and production strategies.

Ashley Innocent

Ashley Innocent

25 March 2026

How to Use Instagram Graph API in 2026

TL;DR

The Instagram Graph API enables developers to manage Instagram Business and Creator accounts programmatically. It uses Facebook Login OAuth 2.0 authentication, GraphQL-based endpoints for content publishing, insights, comments, and messaging, with rate limits of 200 calls per hour per app. This guide covers authentication setup, content publishing, insights retrieval, comment management, and production integration strategies.

Introduction

Instagram has over 2 billion monthly active users, with 200 million+ businesses using Instagram Business accounts. For developers building social media management tools, analytics platforms, or e-commerce integrations, Instagram Graph API integration is essential for reaching this massive audience.

Here’s the reality: social media managers handling 10+ accounts lose 20-30 hours weekly on manual posting, comment responses, and analytics compilation. A solid Instagram API integration automates content publishing, comment moderation, sentiment analysis, and performance reporting.

This guide walks through the complete Instagram Graph API integration process. You’ll learn Facebook Login authentication, content publishing, media insights, comment management, webhook integration, and production deployment strategies. By the end, you’ll have a production-ready Instagram integration.

💡
Apidog simplifies API integration testing. Test your Instagram endpoints, validate OAuth flows, inspect API responses, and debug publishing issues in one workspace. Import API specifications, mock responses, and share test scenarios with your team.
button

What Is the Instagram Graph API?

Instagram Graph API provides programmatic access to Instagram Business and Creator accounts through the Facebook Graph API. The API handles:

Key Features

Feature Description
Graph-based API Node-based resource access
OAuth 2.0 Facebook Login authentication
Webhooks Real-time notifications for comments, mentions
Rate Limiting 200 calls per hour per app
Content Publishing Photos, videos, reels, carousels
Insights Engagement, reach, impressions metrics
Moderation Comments, mentions, message management

Account Requirements

Account Type API Access
Business Account Full API access
Creator Account Full API access
Personal Account No API access (must convert)
Private Account Limited insights

API Architecture Overview

Instagram uses Facebook Graph API structure:

https://graph.facebook.com/v18.0/

API Versions Compared

Version Status End Date Use Case
v18.0 Current March 2026 All new integrations
v17.0 Deprecated January 2026 Existing integrations
v16.0 Retired Expired Do not use

Facebook releases new versions quarterly. Always target the latest stable version.

Getting Started: Authentication Setup

Step 1: Create Facebook Developer Account

Before accessing the API:

  1. Visit the Facebook Developers Portal
  2. Sign in with Facebook account
  3. Create a Facebook App (type: Business)
  4. Add Instagram Graph API product

Connect Instagram to Facebook Page:

  1. Go to Facebook Page Settings > Instagram
  2. Click Connect Account
  3. Log in to Instagram and authorize
  4. Confirm Instagram Business account is linked

Note: Personal Instagram accounts cannot use the Graph API. Convert to Business or Creator account in Instagram Settings.

Step 3: Get Access Tokens

Generate User Access Token:

const FB_APP_ID = process.env.FB_APP_ID;
const FB_APP_SECRET = process.env.FB_APP_SECRET;
const FB_REDIRECT_URI = process.env.FB_REDIRECT_URI;

// Build authorization URL
const getAuthUrl = (state) => {
  const params = new URLSearchParams({
    client_id: FB_APP_ID,
    redirect_uri: FB_REDIRECT_URI,
    scope: 'instagram_basic,instagram_content_publish,instagram_manage_comments,instagram_manage_insights,pages_read_engagement',
    state: state
  });

  return `https://www.facebook.com/v18.0/dialog/oauth?${params.toString()}`;
};

Required Permissions

Permission Description
instagram_basic Basic profile info, media list
instagram_content_publish Publish photos, videos, carousels
instagram_manage_comments Read/write comments
instagram_manage_insights Access analytics data
pages_read_engagement Page access for publishing
pages_manage_posts Publish to connected Page

Step 4: Exchange Token for Long-Lived Token

Short-lived tokens expire in 1 hour. Exchange for long-lived token (60 days):

const exchangeForLongLivedToken = async (shortLivedToken) => {
  const response = await fetch(
    `https://graph.facebook.com/v18.0/oauth/access_token?` +
    `grant_type=fb_exchange_token&` +
    `client_id=${FB_APP_ID}&` +
    `client_secret=${FB_APP_SECRET}&` +
    `fb_exchange_token=${shortLivedToken}`
  );

  const data = await response.json();
  return data;
};

// Usage
const longLivedToken = await exchangeForLongLivedToken(shortLivedToken);
console.log(`Token expires: ${new Date(longLivedToken.expires_at * 1000)}`);

Step 5: Get Instagram Business Account ID

Fetch connected Instagram account:

const getInstagramAccountId = async (pageId, accessToken) => {
  const response = await fetch(
    `https://graph.facebook.com/v18.0/${pageId}?fields=instagram_business_account&access_token=${accessToken}`
  );

  const data = await response.json();
  return data.instagram_business_account.id;
};

// Usage
const igAccountId = await getInstagramAccountId('12345678', accessToken);
console.log(`Instagram Account ID: ${igAccountId}`);

Step 6: Make Authenticated API Calls

Create reusable API client:

const IG_BASE_URL = 'https://graph.facebook.com/v18.0';

const instagramRequest = async (endpoint, params = {}) => {
  const url = new URL(`${IG_BASE_URL}${endpoint}`);
  url.searchParams.append('access_token', process.env.INSTAGRAM_ACCESS_TOKEN);

  Object.entries(params).forEach(([key, value]) => {
    url.searchParams.append(key, value);
  });

  const response = await fetch(url.toString());

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Instagram API Error: ${error.error.message}`);
  }

  return response.json();
};

// Usage
const account = await instagramRequest(`/me`);
console.log(`Instagram Account: ${account.username}`);

Content Publishing

Publishing a Photo

Post a photo to Instagram:

const publishPhoto = async (igAccountId, photoData) => {
  // Step 1: Create media container
  const containerResponse = await instagramRequest(`/${igAccountId}/media`, {
    method: 'POST',
    image_url: photoData.imageUrl,
    caption: photoData.caption,
    location_id: photoData.locationId, // Optional
    is_carousel_item: 'false'
  });

  const creationId = containerResponse.id;

  // Step 2: Publish the media
  const publishResponse = await instagramRequest(`/${igAccountId}/media_publish`, {
    method: 'POST',
    creation_id: creationId
  });

  return publishResponse;
};

// Usage
const post = await publishPhoto({
  igAccountId: '17841400000000000',
  imageUrl: 'https://example.com/image.jpg',
  caption: 'Excited to announce our new product! 🚀 #launch #innovation',
  locationId: '123456789' // Optional
});

console.log(`Published media ID: ${post.id}`);

Publishing a Video

Post a video to Instagram:

const publishVideo = async (igAccountId, videoData) => {
  // Step 1: Create media container
  const containerResponse = await instagramRequest(`/${igAccountId}/media`, {
    method: 'POST',
    video_url: videoData.videoUrl,
    cover_url: videoData.coverUrl, // Optional thumbnail
    caption: videoData.caption,
    media_type: 'REELS', // or 'VIDEO' for feed
    share_to_feed: 'true' // For reels
  });

  const creationId = containerResponse.id;

  // Wait for video processing (poll until status is EXPIRED or FINISHED)
  await waitForVideoProcessing(creationId);

  // Step 2: Publish the media
  const publishResponse = await instagramRequest(`/${igAccountId}/media_publish`, {
    method: 'POST',
    creation_id: creationId
  });

  return publishResponse;
};

const waitForVideoProcessing = async (creationId, maxAttempts = 30) => {
  for (let i = 0; i < maxAttempts; i++) {
    const status = await instagramRequest(`/${creationId}`);

    if (status.status_code === 'FINISHED') {
      return true;
    } else if (status.status_code === 'EXPIRED') {
      throw new Error('Video processing expired');
    }

    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  throw new Error('Video processing timeout');
};

Post multiple media items in one post:

const publishCarousel = async (igAccountId, carouselData) => {
  const children = [];

  // Step 1: Create each carousel item
  for (const item of carouselData.items) {
    const containerResponse = await instagramRequest(`/${igAccountId}/media`, {
      method: 'POST',
      [item.type === 'video' ? 'video_url' : 'image_url']: item.url,
      caption: item.caption || '',
      is_carousel_item: 'true'
    });

    children.push(containerResponse.id);
  }

  // Step 2: Create carousel container with children
  const carouselContainerResponse = await instagramRequest(`/${igAccountId}/media`, {
    method: 'POST',
    media_type: 'CAROUSEL',
    children: children.join(','),
    caption: carouselData.caption
  });

  const creationId = carouselContainerResponse.id;

  // Step 3: Publish the carousel
  const publishResponse = await instagramRequest(`/${igAccountId}/media_publish`, {
    method: 'POST',
    creation_id: creationId
  });

  return publishResponse;
};

// Usage
const carousel = await publishCarousel('17841400000000000', {
  caption: 'Product showcase 2026',
  items: [
    { type: 'image', url: 'https://example.com/img1.jpg', caption: 'Product 1' },
    { type: 'image', url: 'https://example.com/img2.jpg', caption: 'Product 2' },
    { type: 'video', url: 'https://example.com/vid1.mp4', caption: 'Demo' }
  ]
});

Media Types

Media Type Parameters Use Case
IMAGE image_url, caption Photo posts
VIDEO video_url, cover_url, caption Video posts
REELS video_url, cover_url, caption, share_to_feed Reels
CAROUSEL children (array), caption Multiple media

Retrieving Media and Insights

Getting User Media

Fetch published media:

const getUserMedia = async (igAccountId, limit = 25) => {
  const response = await instagramRequest(`/${igAccountId}/media`, {
    fields: 'id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count',
    limit: limit.toString()
  });

  return response;
};

// Usage
const media = await getUserMedia('17841400000000000');
media.data.forEach(item => {
  console.log(`${item.media_type}: ${item.caption}`);
  console.log(`Likes: ${item.like_count}, Comments: ${item.comments_count}`);
  console.log(`URL: ${item.permalink}`);
});

Getting Media Insights

Fetch analytics for specific media:

const getMediaInsights = async (mediaId) => {
  const response = await instagramRequest(`/${mediaId}/insights`, {
    fields: 'impressions,reach,engagement,saved,video_views,profile_visits,follows'
  });

  return response;
};

// Usage
const insights = await getMediaInsights('17890000000000000');
insights.data.forEach(metric => {
  console.log(`${metric.name}: ${metric.values[0].value}`);
});

Available Insights Metrics

Metric Description Media Types
impressions Total views All
reach Unique accounts reached All
engagement Likes + comments + saves All
saved Times saved All
video_views Video views (3+ seconds) Video, Reels
plays Total video plays Video, Reels
profile_visits Profile visits from post All
follows Follows from post All
comments Comment count All
like_count Like count All

Getting Account Insights

Fetch aggregate account analytics:

const getAccountInsights = async (igAccountId, metricNames, since = null, until = null) => {
  const params = {
    metric: metricNames.join(','),
    period: 'day'
  };

  if (since) params.since = since;
  if (until) params.until = until;

  const response = await instagramRequest(`/${igAccountId}/insights`, params);

  return response;
};

// Usage - Get last 30 days of metrics
const accountInsights = await getAccountInsights(
  '17841400000000000',
  ['impressions', 'reach', 'profile_views', 'email_contacts', 'website_clicks'],
  '2026-02-23',
  '2026-03-25'
);

accountInsights.data.forEach(metric => {
  console.log(`${metric.name}:`);
  metric.values.forEach(value => {
    console.log(`  ${value.end_time}: ${value.value}`);
  });
});

Account-Level Metrics

Metric Description
impressions Total profile + content views
reach Unique accounts reached
profile_views Profile visits
website_clicks Link in bio clicks
email_contacts Email button taps
phone_call_clicks Phone button taps
text_message_clicks SMS button taps
get_directions_clicks Address clicks
follower_count Total followers
audience_city Follower cities
audience_country Follower countries
audience_gender_age Demographic breakdown

Comment Management

Getting Comments

Fetch comments on media:

const getMediaComments = async (mediaId, limit = 50) => {
  const response = await instagramRequest(`/${mediaId}/comments`, {
    fields: 'id,text,timestamp,username,hidden',
    limit: limit.toString()
  });

  return response;
};

// Usage
const comments = await getMediaComments('17890000000000000');
comments.data.forEach(comment => {
  console.log(`@${comment.username}: ${comment.text}`);
  console.log(`Hidden: ${comment.hidden}`);
});

Replying to Comments

Post reply to comment:

const replyToComment = async (mediaId, commentId, replyText) => {
  const response = await instagramRequest(`/${mediaId}/comments`, {
    method: 'POST',
    response_to: commentId,
    message: replyText
  });

  return response;
};

// Usage
const reply = await replyToComment(
  '17890000000000000',
  '17900000000000000',
  'Thank you for your interest! Check your DM for details.'
);
console.log(`Reply posted: ${reply.id}`);

Hiding Comments

Hide inappropriate comments:

const hideComment = async (commentId) => {
  const response = await instagramRequest(`/${commentId}`, {
    method: 'POST',
    hide: 'true'
  });

  return response;
};

// Usage
await hideComment('17900000000000000');
console.log('Comment hidden');

Deleting Comments

Remove spam or inappropriate comments:

const deleteComment = async (commentId) => {
  await instagramRequest(`/${commentId}`, {
    method: 'DELETE'
  });

  console.log('Comment deleted');
};

Webhooks

Configuring Webhooks

Set up webhooks for real-time notifications:

const subscribeToWebhooks = async (appId, pageId, accessToken) => {
  // Subscribe to Instagram events
  const response = await fetch(
    `https://graph.facebook.com/v18.0/${appId}/subscriptions`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        object: 'instagram',
        callback_url: 'https://myapp.com/webhooks/instagram',
        verify_token: process.env.WEBHOOK_VERIFY_TOKEN,
        access_token: accessToken,
        fields: ['comments', 'mentions', 'message_reactions']
      })
    }
  );

  return response.json();
};

Handling Webhooks

const express = require('express');
const app = express();

// Verify webhook subscription
app.get('/webhooks/instagram', (req, res) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === process.env.WEBHOOK_VERIFY_TOKEN) {
    console.log('Webhook verified');
    res.status(200).send(challenge);
  } else {
    res.status(403).send('Verification failed');
  }
});

// Handle webhook events
app.post('/webhooks/instagram', express.json(), async (req, res) => {
  const body = req.body;

  if (body.object !== 'instagram') {
    return res.status(404).send('Not found');
  }

  for (const entry of body.entry) {
    const igId = entry.id;
    const changes = entry.changes;

    for (const change of changes) {
      switch (change.field) {
        case 'comments':
          await handleNewComment(change.value);
          break;
        case 'mentions':
          await handleMention(change.value);
          break;
        case 'message_reactions':
          await handleReaction(change.value);
          break;
      }
    }
  }

  res.status(200).send('OK');
});

async function handleNewComment(data) {
  console.log(`New comment on media ${data.media_id}`);
  console.log(`From: ${data.from_id}`);
  console.log(`Text: ${data.text}`);

  // Auto-reply or moderate
  if (isSpam(data.text)) {
    await hideComment(data.id);
  }
}

Webhook Fields

Field Trigger
comments New comment or reply
mentions User mentions account
message_reactions Reaction to story
story_status Story reply/view

Rate Limiting

Understanding Rate Limits

Instagram Graph API enforces:

Exceeding limits results in HTTP 400 with error subcode 613.

Rate Limit Best Practices

  1. Cache responses - Don’t re-fetch unchanged data
  2. Batch requests - Use field expansion to reduce calls
  3. Use webhooks - Real-time updates instead of polling
  4. Implement backoff - Exponential backoff on 429 errors
const makeRateLimitedRequest = async (endpoint, params = {}, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await instagramRequest(endpoint, params);
      return response;
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited. Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
};

Troubleshooting Common Issues

Issue: OAuth Token Expired

Symptoms: Getting “Invalid OAuth access token” errors.

Solutions:

  1. Implement token refresh before 60-day expiry
  2. Store token expiry date and alert before expiration
  3. Re-authenticate user if token expired

Issue: Media Publish Fails

Symptoms: Publishing returns error.

Solutions:

  1. Verify image URL is publicly accessible (no auth required)
  2. Check image format (JPEG, PNG) and size (<8MB)
  3. Ensure video is MP4 format, <1GB, <90 seconds
  4. Wait for video processing before publishing

Issue: Insights Not Available

Symptoms: Insights API returns empty data.

Solutions:

  1. Verify account is Business or Creator (not Personal)
  2. Wait 24-48 hours for insights to populate
  3. Check account has sufficient activity

Production Deployment Checklist

Before going live:


Real-World Use Cases

Social Media Scheduling Tool

A marketing platform automates posting:

Key implementation:

Customer Service Automation

An e-commerce brand automates comment responses:

Key implementation:

Conclusion

The Instagram Graph API provides comprehensive access to Instagram Business and Creator account features. Key takeaways:

button

FAQ Section

How do I get access to Instagram API?

Create a Facebook Developer account, create a Business app, add Instagram Graph API product, and authenticate via Facebook Login with required permissions.

Can I post to Instagram automatically?

Yes, use the Content Publishing API to publish photos, videos, reels, and carousels to Business and Creator accounts.

What types of Instagram accounts support the API?

Only Business and Creator accounts have full API access. Personal accounts have limited or no API access.

How do I get comments from Instagram?

Use the Comments endpoint (/{media-id}/comments) to fetch comments on specific media. Webhooks provide real-time notifications.

What are Instagram rate limits?

The Instagram Graph API allows 200 calls per hour per app. Some endpoints have additional per-user limits.

Can I publish Stories via the API?

Yes, Stories can be published using the same content publishing flow as feed posts.

How do I access Instagram Insights?

Request the instagram_manage_insights permission during OAuth. Use the Insights endpoint to fetch metrics for media and account.

Can I reply to comments automatically?

Yes, use the Comments API to post replies. Many brands use this for automated customer service responses.

Explore more

How to Validate API responses inside Playwright tests ?

How to Validate API responses inside Playwright tests ?

Validate APIs in Playwright tests by sharing OpenAPI fixtures, intercepting network calls, and running Apidog scenarios in CI. Working TypeScript code inside.

12 May 2026

How to Fix 'Invalid custom3p enterprise config' Error in Claude Code

How to Fix 'Invalid custom3p enterprise config' Error in Claude Code

Fix Claude Code's 'Invalid custom3p enterprise config' error fast. Six root causes with exact fixes: URL format, auth variables, settings.json, onboarding, header forwarding, and enterprise policy conflicts.

11 May 2026

Get Free Unlimited Gemini API

Get Free Unlimited Gemini API

Use the full Gemini family (2.5 Pro, 2.5 Flash, 3 Flash Preview) and Gemma open weights free and unlimited via Puter.js. No Google API key, no Cloud project. Code, streaming, vision.

9 May 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs