DEV Community

Preecha
Preecha

Posted on

How to Use Instagram Graph API in 2026

TL;DR

The Instagram Graph API lets you manage Instagram Business and Creator accounts programmatically. Use Facebook Login OAuth 2.0 for authentication, then publish content, retrieve insights, manage comments, and receive webhook events. Plan around the documented rate limit of 200 calls per hour per app.

Try Apidog today

Introduction

Instagram has over 2 billion monthly active users, and more than 200 million businesses use Instagram Business accounts. If you are building a social media scheduler, analytics dashboard, e-commerce workflow, or moderation tool, the Instagram Graph API provides the integration layer.

A production integration can automate publishing, comment moderation, sentiment workflows, and reporting across multiple accounts. This guide covers the implementation path:

  1. Configure Facebook Login OAuth.
  2. Retrieve the connected Instagram account ID.
  3. Publish photos, videos, reels, and carousels.
  4. Query media and account insights.
  5. Manage comments.
  6. Process real-time webhook events.
  7. Handle rate limits and production concerns.

💡 Apidog can help test Instagram endpoints, validate OAuth flows, inspect API responses, mock responses, and share API test scenarios with your team.

What Is the Instagram Graph API?

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

Use it for:

  • Publishing photos, videos, reels, and carousels
  • Retrieving media insights and account analytics
  • Managing comments and mentions
  • Direct messaging through Instagram Graph API and Messenger Platform
  • Tracking hashtags and mentions
  • Managing stories
  • Working with shopping and product tags

Key features

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

Account requirements

Account type API access
Business Account Full API access
Creator Account Full API access
Personal Account No API access; convert first
Private Account Limited insights

API architecture

Instagram uses the Facebook Graph API URL structure:

https://graph.facebook.com/v18.0/
Enter fullscreen mode Exit fullscreen mode

API versions

Version Status End date Use case
v18.0 Current March 2026 New integrations
v17.0 Deprecated January 2026 Existing integrations
v16.0 Retired Expired Do not use

Facebook releases new versions quarterly. Target the latest stable version for new work.

Getting Started: Authentication Setup

Step 1: Create a Facebook app

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

Step 2: Link an Instagram Business account

Connect the Instagram account to a Facebook Page:

  1. Open Facebook Page Settings > Instagram.
  2. Select Connect Account.
  3. Log in to Instagram and authorize the connection.
  4. Confirm that the Instagram Business or Creator account is linked.

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

Step 3: Start Facebook Login

Build an OAuth authorization URL with the permissions your app needs:

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

function 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'
    ].join(','),
    state
  });

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

Redirect the user to getAuthUrl(state). After consent, Facebook redirects back to your FB_REDIRECT_URI with an authorization code that you exchange for an access token.

Required permissions

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

Step 4: Exchange for a long-lived token

Short-lived tokens expire in one hour. Exchange them for long-lived tokens, which expire after 60 days.

const FB_APP_SECRET = process.env.FB_APP_SECRET;

async function exchangeForLongLivedToken(shortLivedToken) {
  const params = new URLSearchParams({
    grant_type: 'fb_exchange_token',
    client_id: FB_APP_ID,
    client_secret: FB_APP_SECRET,
    fb_exchange_token: shortLivedToken
  });

  const response = await fetch(
    `https://graph.facebook.com/v18.0/oauth/access_token?${params}`
  );

  if (!response.ok) {
    throw new Error(`Token exchange failed: ${await response.text()}`);
  }

  return response.json();
}

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

Store the token encrypted and persist its expiration date so you can alert users before it expires.

Step 5: Retrieve the Instagram Business account ID

Fetch the Instagram account connected to a Facebook Page:

async function getInstagramAccountId(pageId, accessToken) {
  const params = new URLSearchParams({
    fields: 'instagram_business_account',
    access_token: accessToken
  });

  const response = await fetch(
    `https://graph.facebook.com/v18.0/${pageId}?${params}`
  );

  if (!response.ok) {
    throw new Error(`Failed to fetch Instagram account: ${await response.text()}`);
  }

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

// Usage
const igAccountId = await getInstagramAccountId(
  '12345678',
  process.env.INSTAGRAM_ACCESS_TOKEN
);

console.log(`Instagram Account ID: ${igAccountId}`);
Enter fullscreen mode Exit fullscreen mode

Step 6: Create a reusable API client

Use one helper for GET, POST, and DELETE requests. The Graph API accepts request parameters as URL-encoded form data for POST requests.

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

async function instagramRequest(endpoint, { method = 'GET', ...params } = {}) {
  const url = new URL(`${IG_BASE_URL}${endpoint}`);
  const accessToken = process.env.INSTAGRAM_ACCESS_TOKEN;

  if (method === 'GET') {
    url.searchParams.set('access_token', accessToken);

    for (const [key, value] of Object.entries(params)) {
      if (value !== undefined && value !== null) {
        url.searchParams.set(key, String(value));
      }
    }
  }

  const options = { method };

  if (method !== 'GET') {
    const body = new URLSearchParams({
      access_token: accessToken
    });

    for (const [key, value] of Object.entries(params)) {
      if (value !== undefined && value !== null) {
        body.set(key, String(value));
      }
    }

    options.body = body;
  }

  const response = await fetch(url, options);
  const data = await response.json();

  if (!response.ok) {
    throw new Error(
      `Instagram API Error: ${data.error?.message ?? 'Unknown error'}`
    );
  }

  return data;
}

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

Content Publishing

Content publishing uses two steps:

  1. Create a media container with /{ig-account-id}/media.
  2. Publish that container with /{ig-account-id}/media_publish.

Publish a photo

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

  return instagramRequest(`/${igAccountId}/media_publish`, {
    method: 'POST',
    creation_id: container.id
  });
}

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

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

Publish a video or reel

Videos must finish processing before publishing. Poll the media container status before calling media_publish.

async function waitForVideoProcessing(creationId, maxAttempts = 30) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const status = await instagramRequest(`/${creationId}`, {
      fields: 'status_code'
    });

    if (status.status_code === 'FINISHED') {
      return;
    }

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

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

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

async function publishVideo(igAccountId, videoData) {
  const container = await instagramRequest(`/${igAccountId}/media`, {
    method: 'POST',
    video_url: videoData.videoUrl,
    cover_url: videoData.coverUrl,
    caption: videoData.caption,
    media_type: videoData.mediaType ?? 'REELS',
    share_to_feed: 'true'
  });

  await waitForVideoProcessing(container.id);

  return instagramRequest(`/${igAccountId}/media_publish`, {
    method: 'POST',
    creation_id: container.id
  });
}

// Usage
const reel = await publishVideo('17841400000000000', {
  videoUrl: 'https://example.com/video.mp4',
  coverUrl: 'https://example.com/cover.jpg',
  caption: 'A product demo in 30 seconds.',
  mediaType: 'REELS'
});

console.log(`Published reel ID: ${reel.id}`);
Enter fullscreen mode Exit fullscreen mode

Publish a carousel

Create a container for every child item, create a carousel container that references them, then publish the carousel container.

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

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

    children.push(container.id);
  }

  const carouselContainer = await instagramRequest(`/${igAccountId}/media`, {
    method: 'POST',
    media_type: 'CAROUSEL',
    children: children.join(','),
    caption: carouselData.caption
  });

  return instagramRequest(`/${igAccountId}/media_publish`, {
    method: 'POST',
    creation_id: carouselContainer.id
  });
}

// 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'
    }
  ]
});

console.log(`Published carousel ID: ${carousel.id}`);
Enter fullscreen mode Exit fullscreen mode

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, caption Multiple media items

Retrieving Media and Insights

Get published media

async function getUserMedia(igAccountId, limit = 25) {
  return instagramRequest(`/${igAccountId}/media`, {
    fields: [
      'id',
      'caption',
      'media_type',
      'media_url',
      'permalink',
      'timestamp',
      'like_count',
      'comments_count'
    ].join(','),
    limit
  });
}

// Usage
const media = await getUserMedia('17841400000000000');

for (const item of media.data) {
  console.log(`${item.media_type}: ${item.caption}`);
  console.log(`Likes: ${item.like_count}, Comments: ${item.comments_count}`);
  console.log(`URL: ${item.permalink}`);
}
Enter fullscreen mode Exit fullscreen mode

Get media insights

async function getMediaInsights(mediaId) {
  return instagramRequest(`/${mediaId}/insights`, {
    fields: 'impressions,reach,engagement,saved,video_views,profile_visits,follows'
  });
}

// Usage
const insights = await getMediaInsights('17890000000000000');

for (const metric of insights.data) {
  console.log(`${metric.name}: ${metric.values[0].value}`);
}
Enter fullscreen mode Exit fullscreen mode

Available media metrics

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

Get account insights

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

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

  return instagramRequest(`/${igAccountId}/insights`, params);
}

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

for (const metric of accountInsights.data) {
  console.log(`${metric.name}:`);

  for (const value of metric.values) {
    console.log(`  ${value.end_time}: ${value.value}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Account-level metrics

Metric Description
impressions Total profile and 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

Get comments for a media item

async function getMediaComments(mediaId, limit = 50) {
  return instagramRequest(`/${mediaId}/comments`, {
    fields: 'id,text,timestamp,username,hidden',
    limit
  });
}

// Usage
const comments = await getMediaComments('17890000000000000');

for (const comment of comments.data) {
  console.log(`@${comment.username}: ${comment.text}`);
  console.log(`Hidden: ${comment.hidden}`);
}
Enter fullscreen mode Exit fullscreen mode

Reply to a comment

async function replyToComment(mediaId, commentId, replyText) {
  return instagramRequest(`/${mediaId}/comments`, {
    method: 'POST',
    response_to: commentId,
    message: replyText
  });
}

// Usage
const reply = await replyToComment(
  '17890000000000000',
  '17900000000000000',
  'Thank you for your interest! Check your DM for details.'
);

console.log(`Reply posted: ${reply.id}`);
Enter fullscreen mode Exit fullscreen mode

Hide a comment

async function hideComment(commentId) {
  return instagramRequest(`/${commentId}`, {
    method: 'POST',
    hide: 'true'
  });
}

// Usage
await hideComment('17900000000000000');
console.log('Comment hidden');
Enter fullscreen mode Exit fullscreen mode

Delete a comment

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

  console.log('Comment deleted');
}
Enter fullscreen mode Exit fullscreen mode

Webhooks

Webhooks reduce polling and let your application react to comments, mentions, and message reactions in real time.

Configure webhook subscriptions

async function subscribeToWebhooks(appId, accessToken) {
  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();
}
Enter fullscreen mode Exit fullscreen mode

Verify and handle webhook events

Return the challenge during the verification request. For incoming events, validate the event object and acknowledge the request after processing.

const express = require('express');

const app = express();

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');
    return res.status(200).send(challenge);
  }

  return res.status(403).send('Verification failed');
});

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) {
    for (const change of entry.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;
      }
    }
  }

  return 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}`);

  if (isSpam(data.text)) {
    await hideComment(data.id);
  }
}
Enter fullscreen mode Exit fullscreen mode

Webhook fields

Field Trigger
comments New comment or reply
mentions A user mentions the account
message_reactions Reaction to a story
story_status Story reply or view

Rate Limiting

Understand the limits

Instagram Graph API enforces:

  • 200 calls per hour per app, shared across users
  • 200 Business Discovery calls per hour per user
  • Content publishing limits based on action type

Exceeding a limit results in HTTP 400 with error subcode 613.

Apply rate-limit best practices

  • Cache responses instead of repeatedly fetching unchanged data.
  • Use field expansion to reduce request counts.
  • Prefer webhooks over polling.
  • Queue requests when processing many accounts.
  • Retry transient failures with exponential backoff.
async function makeRateLimitedRequest(
  endpoint,
  params = {},
  maxRetries = 3
) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await instagramRequest(endpoint, params);
    } catch (error) {
      const isRateLimited =
        error.message.includes('429') || error.message.includes('613');

      if (!isRateLimited || attempt === maxRetries) {
        throw error;
      }

      const delay = 2 ** attempt * 1000;

      console.log(`Rate limited. Retrying in ${delay}ms...`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting Common Issues

OAuth token expired

Symptoms: Invalid OAuth access token errors.

Fix:

  • Track the 60-day token expiration time.
  • Alert users before expiration.
  • Implement token refresh before expiry.
  • Re-authenticate the user if the token has expired.

Media publishing fails

Symptoms: The publishing request returns an error.

Fix:

  • Ensure the image or video URL is publicly accessible without authentication.
  • Verify image format is JPEG or PNG and smaller than 8 MB.
  • Verify video format is MP4, smaller than 1 GB, and shorter than 90 seconds.
  • Poll video processing status before publishing the container.

Insights are unavailable

Symptoms: The Insights API returns empty data.

Fix:

  • Confirm that the account is Business or Creator, not Personal.
  • Allow 24–48 hours for insights to populate.
  • Check that the account has sufficient activity.

Production Deployment Checklist

Before going live:

  • [ ] Convert all test accounts to Business or Creator accounts.
  • [ ] Implement OAuth 2.0 with long-lived tokens.
  • [ ] Encrypt tokens at rest.
  • [ ] Track expiration and implement token refresh.
  • [ ] Expose webhook endpoints over HTTPS.
  • [ ] Add request queuing and rate-limit handling.
  • [ ] Implement structured API error handling.
  • [ ] Log API requests and responses securely.
  • [ ] Create content moderation workflows.
  • [ ] Test across multiple account types.

Real-World Use Cases

Social media scheduling tool

A marketing platform can automate posting across 50+ client accounts.

Implementation approach:

  • Build a content calendar with drag-and-drop scheduling.
  • Store approved media URLs and captions.
  • Create a publishing job for each scheduled item.
  • Publish photos, videos, reels, and carousels through the content publishing flow.
  • Add hashtag suggestions based on content.

Outcome: Less manual publishing work and a more consistent posting schedule.

Customer service automation

An e-commerce brand can respond to common questions through comment webhooks.

Implementation approach:

  • Subscribe to the comments webhook field.
  • Detect keywords such as price, availability, and shipping.
  • Reply with product links for known questions.
  • Route complex questions to a human support queue.
  • Hide spam or inappropriate comments automatically.

Outcome: Faster responses while reserving human support time for complex requests.

Conclusion

The Instagram Graph API provides programmatic access to Instagram Business and Creator account workflows.

Key implementation takeaways:

  • Authenticate with Facebook Login OAuth 2.0 and use 60-day long-lived tokens.
  • Publish photos, videos, reels, and carousels through media containers.
  • Use media and account insights for engagement and reach reporting.
  • Use webhooks for real-time comment and mention handling.
  • Design around the 200-calls-per-hour-per-app rate limit.
  • Use API testing and collaboration tools such as Apidog to validate requests and OAuth flows.

FAQ

How do I get access to the Instagram API?

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

Can I post to Instagram automatically?

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

What Instagram account types support the API?

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

How do I get Instagram comments?

Use the /{media-id}/comments endpoint to retrieve comments for a specific media item. Use webhooks for real-time comment notifications.

What are Instagram rate limits?

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

Can I publish Stories through the API?

Yes. Stories can be published through the same content publishing flow used for feed posts.

How do I access Instagram Insights?

Request instagram_manage_insights during OAuth, then use the Insights endpoint to retrieve media and account metrics.

Can I reply to comments automatically?

Yes. Use the Comments API to create replies. This is commonly used for automated customer service workflows.

Top comments (0)