TL;DR
The Instagram Graph API allows developers to programmatically manage Instagram Business and Creator accounts. It uses Facebook Login with OAuth 2.0 for authentication and provides endpoints for content publishing, analytics, comments, and messaging. Rate limits are 200 calls per hour per app. This guide walks through authentication, publishing, insights, comment management, webhook setup, and production integration.
Introduction
Instagram has over 2 billion monthly active users and 200M+ businesses using Instagram Business accounts. If you’re building social media management, analytics, or e-commerce tools, Instagram Graph API integration is essential for automating workflows at scale.
Manual posting, comment moderation, and reporting for multiple accounts is a huge time sink. Instagram Graph API lets you automate publishing, comment handling, sentiment analysis, and analytics so you can save 20–30 hours per week per manager.
This guide shows step-by-step how to integrate with the Instagram Graph API: setup, OAuth, publishing, insights, comment management, webhooks, and deployment, so you can build production-grade automation.
💡 Apidog simplifies API integration testing. Test Instagram endpoints, validate OAuth flows, inspect API responses, and debug publishing issues in a single workspace. Import API specs, mock responses, and share test scenarios with your team.
What Is the Instagram Graph API?
The Instagram Graph API provides programmatic access to Instagram Business and Creator accounts via the Facebook Graph API. Key features include:
- Content publishing: photos, videos, reels, carousels
- Media insights & analytics
- Comment and mention management
- Direct messaging (with Messenger Platform)
- Hashtag/mention tracking
- Story management
- Shopping/product tags
Key Features
| Feature | Description |
|---|---|
| Graph-based API | Node-based resource access |
| OAuth 2.0 | Facebook Login authentication |
| Webhooks | Real-time notifications (comments, mentions) |
| Rate Limiting | 200 calls/hour/app |
| Content Publishing | Photos, videos, reels, carousels |
| Insights | Engagement, reach, impressions |
| 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
All endpoints follow the 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 | Jan 2026 | Existing integrations |
| v16.0 | Retired | Expired | Do not use |
Tip: Always target the latest stable version.
Getting Started: Authentication Setup
Step 1: Create Facebook Developer Account
- Go to the Facebook Developers Portal
- Sign in with your Facebook account
- Create a new Facebook App (type: Business)
- Add the Instagram Graph API product
Step 2: Link Instagram Business Account
- In Facebook Page Settings, go to Instagram
- Click Connect Account
- Log in to your Instagram and authorize
- Confirm your Instagram Business account is linked
Personal accounts can’t use the Graph API—convert to Business or Creator in Instagram Settings.
Step 3: Get Access Tokens
Build the Facebook OAuth authorization URL:
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;
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 a 60-day token:
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
Retrieve the Instagram account ID linked to your page:
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 a 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
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
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');
};
Publishing a Carousel (Multiple Images/Videos)
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
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
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+ sec) | 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
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 - Last 30 days
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
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
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
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
const deleteComment = async (commentId) => {
await instagramRequest(`/${commentId}`, {
method: 'DELETE'
});
console.log('Comment deleted');
};
Webhooks
Configuring Webhooks
Subscribe to Instagram webhook events:
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
The Instagram Graph API enforces:
- 200 calls/hour/app (shared by all users)
- Business Discovery: 200 calls/hour/user
- Content Publishing: Limited by action
If you exceed the limit, you’ll get HTTP 400 with error subcode 613.
Rate Limit Best Practices
- Cache responses — Avoid redundant fetches
- Batch requests — Use field expansion to reduce calls
- Use webhooks — Real-time updates, avoid polling
- 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: “Invalid OAuth access token” errors.
Solutions:
- Refresh token before expiry (60 days)
- Store expiry date and alert users
- Re-authenticate user if token is expired
Issue: Media Publish Fails
Symptoms: Publishing returns error.
Solutions:
- Image URL must be publicly accessible
- Image format: JPEG, PNG, <8MB
- Video: MP4, <1GB, <90s
- Wait for video processing before publishing
Issue: Insights Not Available
Symptoms: Insights API returns empty data.
Solutions:
- Only Business/Creator accounts are supported
- Wait 24–48h for insights population
- Ensure account has sufficient activity
Production Deployment Checklist
Before going live, make sure to:
- [ ] Convert all test accounts to Business/Creator
- [ ] Use OAuth 2.0 with long-lived tokens
- [ ] Store tokens securely (encrypted)
- [ ] Implement automatic token refresh
- [ ] Set up webhook endpoints with HTTPS
- [ ] Add rate limiting and request queuing
- [ ] Implement robust error handling
- [ ] Add logging for all API calls
- [ ] Create content moderation workflows
- [ ] Test with multiple account types
Real-World Use Cases
Social Media Scheduling Tool
- Challenge: Manual posting across 50+ client accounts
- Solution: Scheduled publishing via Instagram API
- Result: 80% time savings, consistent posting
Key implementation:
- Content calendar with drag-and-drop scheduling
- Auto-publish photos, videos, carousels
- Hashtag suggestions based on content
Customer Service Automation
- Challenge: Slow response to customer inquiries
- Solution: Auto-reply to common questions via webhook
- Result: 5-min average response, 90% satisfaction
Key implementation:
- Keyword detection (price, availability, shipping)
- Auto-reply with product links
- Escalate complex queries to human agents
Conclusion
The Instagram Graph API enables automation for Instagram Business and Creator accounts. Key actions:
- Authenticate with Facebook Login OAuth 2.0 (60-day tokens)
- Publish photos, videos, reels, and carousels
- Access insights: engagement, reach, demographics
- Use webhooks for real-time monitoring
- Design around 200 calls/hour/app rate limit
Apidog streamlines API testing and team collaboration for all these workflows.
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.
Top comments (0)