instagram-api
Use when publishing organic content to Instagram via the Instagram Graph API — photos, Reels, Stories, carousels, and business account management. Organic publishing only, NOT paid Instagram ads.
| Model | Source |
|---|---|
| sonnet | pack: social |
Full Reference
Instagram API
Section titled “Instagram API”Instagram Graph API — organic content publishing for business and creator accounts. Photos, Reels, Stories, carousels. Not for paid ads (use meta-ads).
Mandatory Announcement — FIRST OUTPUT before anything else:
┏━ 📸 instagram-api ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ [one-line: what Instagram publishing task] ┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛In scope: Organic photo posts, Reels, Stories, carousels, captions, hashtags, tagged locations, mentions, media insights, content scheduling, comment management.
Out of scope: Paid Instagram ads → meta-ads. Instagram Shopping catalog → separate Commerce API. Direct Messages → Instagram Messaging API (separate scope).
⚠ Basic Display API is sunset (December 2024). Do not use it. The Instagram Graph API (documented here) is the only supported path for business and creator accounts.
Requirements
Section titled “Requirements”- Account type: Instagram Business or Creator account (personal accounts not supported)
- Connection: Must be connected to a Facebook Page
- Auth: OAuth 2.0 via Meta for Developers app
- Permissions:
instagram_content_publish,instagram_manage_insights,pages_read_engagement
Auth Flow
Section titled “Auth Flow”# 1. Get user's Instagram Business Account IDGET /v21.0/me/accounts?access_token={USER_TOKEN}# Returns pages — then:GET /v21.0/{page-id}?fields=instagram_business_account&access_token={PAGE_TOKEN}# Returns: { "instagram_business_account": { "id": "IG_USER_ID" } }Store IG_USER_ID — used in all publishing endpoints.
Publishing Flow
Section titled “Publishing Flow”Instagram publishing is always a two-step process: create container → publish.
Photo Post
Section titled “Photo Post”# Step 1: Create media containerPOST /v21.0/{ig-user-id}/media{ "image_url": "https://example.com/photo.jpg", "caption": "Caption with #hashtags @mentions", "location_id": "optional_facebook_place_id", "access_token": "{PAGE_ACCESS_TOKEN}"}# Returns: { "id": "CONTAINER_ID" }
# Step 2: Publish containerPOST /v21.0/{ig-user-id}/media_publish{ "creation_id": "{CONTAINER_ID}", "access_token": "{PAGE_ACCESS_TOKEN}"}# Returns: { "id": "MEDIA_ID" }Image requirements: JPEG only, min 320px, max 1440px width, max 8MB, publicly accessible URL.
# Step 1: Create Reel containerPOST /v21.0/{ig-user-id}/media{ "media_type": "REELS", "video_url": "https://example.com/reel.mp4", "caption": "Reel caption #hashtag", "share_to_feed": true, "access_token": "{PAGE_ACCESS_TOKEN}"}# Returns: { "id": "CONTAINER_ID" }
# Wait for processing — poll until status = FINISHEDGET /v21.0/{CONTAINER_ID}?fields=status_code&access_token={PAGE_ACCESS_TOKEN}# status_code: IN_PROGRESS → FINISHED (or ERROR)
# Step 2: Publish when FINISHEDPOST /v21.0/{ig-user-id}/media_publish{ "creation_id": "{CONTAINER_ID}", "access_token": "{PAGE_ACCESS_TOKEN}"}Reel requirements: MP4/MOV, H.264, AAC audio, 3-90 seconds, max 1GB, 9:16 aspect ratio recommended. Video must be publicly accessible during upload.
# Photo StoryPOST /v21.0/{ig-user-id}/media{ "media_type": "STORIES", "image_url": "https://example.com/story.jpg", "access_token": "{PAGE_ACCESS_TOKEN}"}
# Video StoryPOST /v21.0/{ig-user-id}/media{ "media_type": "STORIES", "video_url": "https://example.com/story.mp4", "access_token": "{PAGE_ACCESS_TOKEN}"}
# Publish (same as other types)POST /v21.0/{ig-user-id}/media_publish{ "creation_id": "{CONTAINER_ID}", "access_token": "{PAGE_ACCESS_TOKEN}"}Story requirements: Stories disappear after 24h. 9:16 ratio. Max 60s for video. No caption support.
Carousel (Multi-Image/Video)
Section titled “Carousel (Multi-Image/Video)”# Step 1: Create item containers (one per image/video)POST /v21.0/{ig-user-id}/media{ "is_carousel_item": true, "image_url": "https://example.com/photo1.jpg", "access_token": "{PAGE_ACCESS_TOKEN}"}# Repeat for each item — collect all ITEM_IDs
# Step 2: Create carousel containerPOST /v21.0/{ig-user-id}/media{ "media_type": "CAROUSEL", "children": ["ITEM_ID_1", "ITEM_ID_2", "ITEM_ID_3"], "caption": "Carousel caption", "access_token": "{PAGE_ACCESS_TOKEN}"}
# Step 3: Publish carousel containerPOST /v21.0/{ig-user-id}/media_publish{ "creation_id": "{CAROUSEL_CONTAINER_ID}", "access_token": "{PAGE_ACCESS_TOKEN}"}Carousel limits: 2-10 items. All items must be same media type (all images or mixed images+videos). Max 8MB per image, max 1GB per video.
Scheduling
Section titled “Scheduling”# Create container with scheduled publish timePOST /v21.0/{ig-user-id}/media{ "image_url": "https://example.com/photo.jpg", "caption": "Scheduled post", "publish_to_scheduled": true, # required for scheduling "scheduled_publish_time": 1735689600, # Unix timestamp, 10min-75 days in future "access_token": "{PAGE_ACCESS_TOKEN}"}Media Insights
Section titled “Media Insights”# Get insights for a published postGET /v21.0/{media-id}/insights?metric=impressions,reach,likes,comments,saves,shares&access_token={PAGE_ACCESS_TOKEN}
# Account-level insightsGET /v21.0/{ig-user-id}/insights?metric=impressions,reach,profile_views&period=day&access_token={PAGE_ACCESS_TOKEN}Available media metrics: impressions, reach, likes, comments, saves, shares, plays (video only), total_interactions
Comment Management
Section titled “Comment Management”# List comments on a postGET /v21.0/{media-id}/comments?access_token={PAGE_ACCESS_TOKEN}
# Reply to a commentPOST /v21.0/{comment-id}/replies{ "message": "Reply text", "access_token": "{PAGE_ACCESS_TOKEN}"}
# Hide a commentPOST /v21.0/{comment-id}{ "hide": true, "access_token": "{PAGE_ACCESS_TOKEN}"}
# Delete a commentDELETE /v21.0/{comment-id}?access_token={PAGE_ACCESS_TOKEN}Node.js Example
Section titled “Node.js Example”const IG_USER_ID = process.env.IG_USER_ID;const IG_TOKEN = process.env.IG_PAGE_ACCESS_TOKEN;const API_VERSION = 'v21.0';const BASE = `https://graph.facebook.com/${API_VERSION}`;
async function publishPhoto(imageUrl, caption) { // Step 1: Create container const containerRes = await fetch(`${BASE}/${IG_USER_ID}/media`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image_url: imageUrl, caption, access_token: IG_TOKEN }), }); const { id: containerId, error } = await containerRes.json(); if (error) throw new Error(`IG container: ${error.message}`);
// Step 2: Publish const publishRes = await fetch(`${BASE}/${IG_USER_ID}/media_publish`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ creation_id: containerId, access_token: IG_TOKEN }), }); const { id: mediaId, error: pubError } = await publishRes.json(); if (pubError) throw new Error(`IG publish: ${pubError.message}`); return mediaId;}
async function pollContainerStatus(containerId, maxAttempts = 10) { for (let i = 0; i < maxAttempts; i++) { const res = await fetch(`${BASE}/${containerId}?fields=status_code&access_token=${IG_TOKEN}`); const { status_code } = await res.json(); if (status_code === 'FINISHED') return true; if (status_code === 'ERROR') throw new Error('Container processing failed'); await new Promise(r => setTimeout(r, 5000)); // 5s between polls } throw new Error('Container polling timeout');}.env Setup
Section titled “.env Setup”IG_USER_ID=your_instagram_business_account_idIG_PAGE_ACCESS_TOKEN=your_page_access_tokenPublishing Limits
Section titled “Publishing Limits”| Limit | Value |
|---|---|
| Posts per 24h | 25 |
| API calls per hour | 200 per token |
| Hashtags per post | 30 (Instagram policy, enforced at app level) |
| Caption length | 2,200 characters |
Common Errors
Section titled “Common Errors”| Code | Meaning | Fix |
|---|---|---|
| 190 | Invalid/expired token | Re-authenticate |
| 10 | Permission denied | Check instagram_content_publish scope |
| 9007 | Media URL not accessible | URL must be publicly reachable (no auth, no redirects) |
| 9004 | Invalid media | Check format/size requirements |
| 24 | Publishing limit reached | Wait until 24h window resets |
API Version
Section titled “API Version”Current stable: v21.0. Pin version in all endpoint URLs.