Skip to content

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.

ModelSource
sonnetpack: social
Full Reference

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.

  • 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
Terminal window
# 1. Get user's Instagram Business Account ID
GET /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.

Instagram publishing is always a two-step process: create container → publish.

Terminal window
# Step 1: Create media container
POST /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 container
POST /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.

Terminal window
# Step 1: Create Reel container
POST /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 = FINISHED
GET /v21.0/{CONTAINER_ID}?fields=status_code&access_token={PAGE_ACCESS_TOKEN}
# status_code: IN_PROGRESS → FINISHED (or ERROR)
# Step 2: Publish when FINISHED
POST /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.

Terminal window
# Photo Story
POST /v21.0/{ig-user-id}/media
{
"media_type": "STORIES",
"image_url": "https://example.com/story.jpg",
"access_token": "{PAGE_ACCESS_TOKEN}"
}
# Video Story
POST /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.

Terminal window
# 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 container
POST /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 container
POST /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.

Terminal window
# Create container with scheduled publish time
POST /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}"
}
Terminal window
# Get insights for a published post
GET /v21.0/{media-id}/insights?metric=impressions,reach,likes,comments,saves,shares&access_token={PAGE_ACCESS_TOKEN}
# Account-level insights
GET /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

Terminal window
# List comments on a post
GET /v21.0/{media-id}/comments?access_token={PAGE_ACCESS_TOKEN}
# Reply to a comment
POST /v21.0/{comment-id}/replies
{
"message": "Reply text",
"access_token": "{PAGE_ACCESS_TOKEN}"
}
# Hide a comment
POST /v21.0/{comment-id}
{
"hide": true,
"access_token": "{PAGE_ACCESS_TOKEN}"
}
# Delete a comment
DELETE /v21.0/{comment-id}?access_token={PAGE_ACCESS_TOKEN}
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');
}
Terminal window
IG_USER_ID=your_instagram_business_account_id
IG_PAGE_ACCESS_TOKEN=your_page_access_token
LimitValue
Posts per 24h25
API calls per hour200 per token
Hashtags per post30 (Instagram policy, enforced at app level)
Caption length2,200 characters
CodeMeaningFix
190Invalid/expired tokenRe-authenticate
10Permission deniedCheck instagram_content_publish scope
9007Media URL not accessibleURL must be publicly reachable (no auth, no redirects)
9004Invalid mediaCheck format/size requirements
24Publishing limit reachedWait until 24h window resets

Current stable: v21.0. Pin version in all endpoint URLs.