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.
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:
- Content publishing (photos, videos, reels, carousels)
- Media insights and analytics
- Comment and mention management
- Direct messaging (via Instagram Graph API + Messenger Platform)
- Hashtag and mention tracking
- Story management
- 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, 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:
- Visit the Facebook Developers Portal
- Sign in with Facebook account
- Create a Facebook App (type: Business)
- Add Instagram Graph API product
Step 2: Link Instagram Business Account
Connect Instagram to Facebook Page:
- Go to Facebook Page Settings > Instagram
- Click Connect Account
- Log in to Instagram and authorize
- 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');
};
Publishing a Carousel (Multiple Images/Videos)
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:
- 200 calls per hour per app (shared across all users)
- Business Discovery: 200 calls per hour per user
- Content Publishing: Limited by action type
Exceeding limits results in HTTP 400 with error subcode 613.
Rate Limit Best Practices
- Cache responses - Don’t re-fetch unchanged data
- Batch requests - Use field expansion to reduce calls
- Use webhooks - Real-time updates instead of 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: Getting “Invalid OAuth access token” errors.
Solutions:
- Implement token refresh before 60-day expiry
- Store token expiry date and alert before expiration
- Re-authenticate user if token expired
Issue: Media Publish Fails
Symptoms: Publishing returns error.
Solutions:
- Verify image URL is publicly accessible (no auth required)
- Check image format (JPEG, PNG) and size (<8MB)
- Ensure video is MP4 format, <1GB, <90 seconds
- Wait for video processing before publishing
Issue: Insights Not Available
Symptoms: Insights API returns empty data.
Solutions:
- Verify account is Business or Creator (not Personal)
- Wait 24-48 hours for insights to populate
- Check account has sufficient activity
Production Deployment Checklist
Before going live:
- [ ] Convert all test accounts to Business/Creator
- [ ] Implement OAuth 2.0 with long-lived tokens
- [ ] Store tokens securely with encryption
- [ ] Implement automatic token refresh
- [ ] Set up webhook endpoints with HTTPS
- [ ] Add rate limiting and request queuing
- [ ] Implement comprehensive 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
A marketing platform automates posting:
- Challenge: Manual posting across 50+ client accounts
- Solution: Scheduled publishing via Instagram API
- Result: 80% time savings, consistent posting schedule
Key implementation:
- Content calendar with drag-and-drop scheduling
- Auto-publish photos, videos, carousels
- Hashtag suggestions based on content
Customer Service Automation
An e-commerce brand automates comment responses:
- Challenge: Slow response to customer inquiries
- Solution: Auto-reply to common questions via webhook
- Result: 5-minute average response time, 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 provides comprehensive access to Instagram Business and Creator account features. Key takeaways:
- Facebook Login OAuth 2.0 authentication with 60-day tokens
- Content publishing supports photos, videos, reels, carousels
- Insights API provides engagement, reach, and demographic data
- Webhooks enable real-time comment and mention monitoring
- Rate limit of 200 calls/hour per app requires careful management
- Apidog streamlines API testing and team collaboration
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.



