{"skill":{"slug":"instagram-page","displayName":"Instagram Page","summary":"Instagram Business manager: post photos, reels, stories & get insights. Requires: powershell/pwsh. Reads ~/.config/instagram-page/credentials.json (IG_ACCESS...","description":"---\nname: instagram-page\ndescription: \"Instagram Business manager: post photos, reels, stories & get insights. Requires: powershell/pwsh. Reads ~/.config/instagram-page/credentials.json (IG_ACCESS_TOKEN, IG_USER_ID). IG_APP_SECRET for one-time setup only — delete afterward. Token expires every 60 days — refresh before expiry. Grant minimal permissions only. No data forwarded to third parties; all calls go to graph.facebook.com only.\"\nmetadata: {\"openclaw\":{\"emoji\":\"[ig]\",\"requires\":{\"anyBins\":[\"powershell\",\"pwsh\"]}}}\n---\n\n# instagram-page - Universal Instagram Graph API Skill\n\nConstructs and executes Instagram Graph API calls inline based on what the user wants. No scripts needed.\n\n> **Requires an Instagram Business or Creator account** linked to a Facebook Page.\n> Personal Instagram accounts are not supported by the Instagram Graph API.\n\nAPI version: **v25.0**\nBase URL: `https://graph.facebook.com/v25.0`\n\n---\n\n## STEP 1 - Load Credentials\n\nCredentials are stored in `~/.config/instagram-page/credentials.json`.\n\n```powershell\n$cfg      = Get-Content \"$HOME/.config/instagram-page/credentials.json\" -Raw | ConvertFrom-Json\n$token    = $cfg.IG_ACCESS_TOKEN\n$igUserId = $cfg.IG_USER_ID\n```\n\n**If the file does not exist**, guide setup. Required fields:\n\n| Field | Purpose |\n|---|---|\n| `IG_ACCESS_TOKEN` | Long-lived User access token - used for all API calls |\n| `IG_USER_ID` | Instagram-scoped Business/Creator User ID (numeric) |\n| `IG_APP_ID` | Meta App ID - only needed during token exchange |\n| `IG_APP_SECRET` | Meta App Secret - only needed during token exchange |\n\n**Prerequisites:**\n1. Instagram account must be a **Business** or **Creator** account\n2. The Instagram account must be **linked to a Facebook Page**\n3. A Meta App with `instagram_basic`, `instagram_content_publish`, `instagram_manage_comments`, `instagram_manage_insights` permissions\n\n**One-time token exchange setup:**\n```powershell\n# Provide: $appId, $appSecret, $shortToken (from Graph API Explorer with IG permissions), $fbPageId\n# 1. Exchange short-lived token for long-lived user token (valid 60 days)\n$r1 = Invoke-RestMethod \"https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=$appId&client_secret=$appSecret&fb_exchange_token=$shortToken\"\n$longToken = $r1.access_token\n# 2. Get Instagram Business Account ID linked to your Facebook Page\n$r2 = Invoke-RestMethod \"https://graph.facebook.com/v25.0/$fbPageId?fields=instagram_business_account&access_token=$longToken\"\n$igUserId = $r2.instagram_business_account.id\n# 3. Save - only these four fields\n@{\n    IG_USER_ID      = $igUserId\n    IG_ACCESS_TOKEN = $longToken\n    IG_APP_ID       = $appId\n    IG_APP_SECRET   = $appSecret\n} | ConvertTo-Json | Set-Content \"$HOME/.config/instagram-page/credentials.json\" -Encoding UTF8\n```\n\n**Restrict file permissions immediately after saving:**\n```powershell\n# Windows\nicacls \"$HOME/.config/instagram-page/credentials.json\" /inheritance:r /grant:r \"$($env:USERNAME):(R,W)\"\n# macOS / Linux\n# chmod 600 ~/.config/instagram-page/credentials.json\n```\n\n**Refresh token before it expires (every ~50 days):**\n```powershell\n$r = Invoke-RestMethod \"https://graph.facebook.com/oauth/access_token?grant_type=ig_refresh_token&access_token=$token\"\n$cfg.IG_ACCESS_TOKEN = $r.access_token\n$cfg | ConvertTo-Json | Set-Content \"$HOME/.config/instagram-page/credentials.json\" -Encoding UTF8\n```\n\n> **Delete IG_APP_SECRET** from credentials.json after setup - it is not needed for API calls.\n> Never commit this file to version control. It contains long-lived secrets.\n> This skill makes no external calls other than to graph.facebook.com. No data is forwarded to third parties.\n\n---\n\n## STEP 2 - Figure Out the API Call\n\nInstagram publishing uses a **two-step flow** for most media types:\n1. **Create a media container** - uploads/registers the media, returns a `creation_id`\n2. **Publish the container** - makes it live on Instagram\n\n### Common Endpoints\n\n| What user wants | Method | Endpoint |\n|---|---|---|\n| Post single photo | POST x2 | Step 1: `/$igUserId/media` Step 2: `/$igUserId/media_publish` |\n| Post reel (video) | POST x2 | Step 1: `/$igUserId/media` (media_type=REELS) Step 2: `/$igUserId/media_publish` |\n| Post story | POST x2 | Step 1: `/$igUserId/media` (media_type=STORIES) Step 2: `/$igUserId/media_publish` |\n| Post carousel | POST x3+ | Step 1: item containers Step 2: carousel container Step 3: publish |\n| Get recent media | GET | `/$igUserId/media?fields=id,caption,media_type,timestamp,like_count,comments_count` |\n| Get account info | GET | `/$igUserId?fields=username,name,biography,followers_count,media_count` |\n| Get comments | GET | `/{media-id}/comments?fields=text,username,timestamp` |\n| Reply to comment | POST | `/{comment-id}/replies` body: `message` |\n| Delete comment | DELETE | `/{comment-id}` |\n| Hide/show comment | POST | `/{comment-id}` body: `hide=true/false` |\n| Get media insights | GET | `/{media-id}/insights?metric=impressions,reach,likes,comments,shares,saved` |\n| Get account insights | GET | `/$igUserId/insights?metric=impressions,reach,follower_count&period=day` |\n| Get hashtag ID | GET | `ig/hashtags?user_id=$igUserId&q=hashtag` |\n| Search hashtag media | GET | `/{hashtag-id}/top_media?user_id=$igUserId&fields=id,caption,media_type` |\n| Get tagged media | GET | `/$igUserId/tags?fields=caption,media_url,timestamp` |\n\n### API Call Patterns\n\n**GET:**\n```powershell\n$result = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/ENDPOINT?access_token=$token\" -ErrorAction Stop\n```\n\n**POST (form body):**\n```powershell\n$result = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/ENDPOINT\" -Method POST `\n    -Body @{ field1=\"value1\"; field2=\"value2\"; access_token=$token } -ErrorAction Stop\n```\n\n**DELETE:**\n```powershell\n$result = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/{id}?access_token=$token\" -Method DELETE -ErrorAction Stop\n```\n\n### Publishing Patterns\n\n**Single photo post:**\n```powershell\n# image_url must be publicly accessible\n$container = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media\" -Method POST `\n    -Body @{ image_url=$imageUrl; caption=$caption; access_token=$token } -ErrorAction Stop\n$post = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media_publish\" -Method POST `\n    -Body @{ creation_id=$container.id; access_token=$token } -ErrorAction Stop\nWrite-Host \"Published post ID: $($post.id)\"\n```\n\n**Reel post (video):**\n```powershell\n# video_url must be publicly accessible MP4 (H.264, AAC audio, max 1GB, max 60s for feed)\n$container = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media\" -Method POST `\n    -Body @{ media_type=\"REELS\"; video_url=$videoUrl; caption=$caption; share_to_feed=\"true\"; access_token=$token } -ErrorAction Stop\n# Poll for processing status - video upload is async\ndo {\n    Start-Sleep -Seconds 5\n    $status = Invoke-RestMethod \"https://graph.facebook.com/v25.0/$($container.id)?fields=status_code&access_token=$token\"\n} while ($status.status_code -eq \"IN_PROGRESS\")\nif ($status.status_code -ne \"FINISHED\") { throw \"Video processing failed: $($status.status_code)\" }\n$post = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media_publish\" -Method POST `\n    -Body @{ creation_id=$container.id; access_token=$token } -ErrorAction Stop\nWrite-Host \"Published reel ID: $($post.id)\"\n```\n\n**Carousel post (2-10 images/videos):**\n```powershell\n# Create item containers for each media URL\n$itemIds = @()\nforeach ($url in $mediaUrls) {\n    $item = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media\" -Method POST `\n        -Body @{ image_url=$url; is_carousel_item=\"true\"; access_token=$token } -ErrorAction Stop\n    $itemIds += $item.id\n}\n# Create carousel container\n$carousel = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media\" -Method POST `\n    -Body @{ media_type=\"CAROUSEL\"; children=($itemIds -join \",\"); caption=$caption; access_token=$token } -ErrorAction Stop\n# Publish\n$post = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media_publish\" -Method POST `\n    -Body @{ creation_id=$carousel.id; access_token=$token } -ErrorAction Stop\nWrite-Host \"Published carousel ID: $($post.id)\"\n```\n\n**Story:**\n```powershell\n$container = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media\" -Method POST `\n    -Body @{ media_type=\"STORIES\"; image_url=$imageUrl; access_token=$token } -ErrorAction Stop\n$post = Invoke-RestMethod -Uri \"https://graph.facebook.com/v25.0/$igUserId/media_publish\" -Method POST `\n    -Body @{ creation_id=$container.id; access_token=$token } -ErrorAction Stop\nWrite-Host \"Published story ID: $($post.id)\"\n```\n\n> **Image/video URLs must be publicly accessible** - local files are not accepted by the API.\n> Upload to a public server or CDN before calling. Instagram limits ~50 API-published posts per 24h.\n\n---\n\n## STEP 3 - Handle Errors\n\n```powershell\ntry {\n    # ... API call ...\n} catch {\n    $err     = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue\n    $code    = $err.error.code\n    $subcode = $err.error.error_subcode\n    $msg     = $err.error.message\n    Write-Host \"Error $code/$subcode : $msg\"\n}\n```\n\n| Code | Subcode | Meaning | Fix |\n|---|---|---|---|\n| 100 | - | Invalid parameter | Check parameter values; image/video URL must be public |\n| 102 | - | Session expired | Refresh token - run the refresh snippet in STEP 1 |\n| 190 | 460 | Token expired (60 days) | Refresh token - run the refresh snippet in STEP 1 |\n| 190 | 467 | Invalid token | Re-run full setup to get a new token |\n| 200 | - | Permission denied | Add the missing permission to your Meta App and regenerate token |\n| 10 | - | Permission denied (IG scope) | Add permission listed in error.message; re-run setup |\n| 24 | - | No linked IG Business account | Link Instagram account to your Facebook Page in Instagram settings |\n| 32 | - | Page request limit reached | Wait and retry; hourly rate limit hit |\n| 368 | - | Temporarily blocked | Wait and retry; account may be temporarily restricted |\n| 9007 | - | Publishing limit reached | Instagram limits ~50 API-published posts per 24h |\n| 2207026 | - | Video format invalid | MP4 required; H.264 video, AAC audio, max 1GB, 60s for feed reels |\n\n### Permissions Reference\n\n| Permission | Required for |\n|---|---|\n| `instagram_basic` | Read account info, media list |\n| `instagram_content_publish` | Create and publish posts, reels, stories, carousels |\n| `instagram_manage_comments` | Read, reply to, hide, delete comments |\n| `instagram_manage_insights` | Access account and media insights |\n| `pages_show_list` | List Facebook Pages (needed during IG account lookup) |\n| `pages_read_engagement` | Read Facebook Page linked to Instagram account |\n\n**If a permission is missing:**\n1. Go to [Meta for Developers](https://developers.facebook.com/apps/)\n2. Select your app -> Permissions and Features\n3. Add the required permission\n4. Regenerate token via [Graph API Explorer](https://developers.facebook.com/tools/explorer/) with the permission checked\n5. Re-run setup with the new token\n\n---\n\n## AGENT RULES\n\n- **Always load credentials first.** If missing or incomplete, guide setup.\n- **Only use `IG_ACCESS_TOKEN` and `IG_USER_ID`** for API calls. `IG_APP_ID` and `IG_APP_SECRET` are for token exchange only.\n- **Delete `IG_APP_SECRET`** from credentials.json after token exchange - it is not needed for API calls.\n- **Token expiry:** `IG_ACCESS_TOKEN` expires every 60 days. Remind the user to refresh before expiry using the refresh snippet in STEP 1. Rotate immediately if the host is ever compromised.\n- **Two-step publishing:** always create a container first, then publish. Never try to publish in a single API call.\n- **Public URLs only:** image/video URLs must be publicly accessible. If the user provides a local file path, tell them to upload it to a public server or CDN first.\n- **Reel processing:** poll `status_code` until `FINISHED` before publishing - video processing is async and can take minutes.\n- **Least-privilege:** only request the permissions the use case needs.\n- **All API calls go to `graph.facebook.com` only.** No external forwarding, no third-party services.\n- **Construct API calls inline** from user intent - do not look for script files.\n- **On any error:** parse `error.code` + `error.error_subcode`, map to the table above, tell the user exactly what to do.\n- **If a permission is missing:** name it, link to Meta for Developers, say to re-run setup.\n- **Business/Creator only:** if the user has a personal Instagram account, tell them to convert it to Business or Creator in Instagram settings first - personal accounts are not supported by the Instagram Graph API.","topics":["Business","Json"],"tags":{"latest":"1.0.1"},"stats":{"comments":0,"downloads":873,"installsAllTime":33,"installsCurrent":0,"stars":2,"versions":2},"createdAt":1772353476061,"updatedAt":1778491678078},"latestVersion":{"version":"1.0.1","createdAt":1772355101476,"changelog":"Improved description for discoverability","license":null},"metadata":{"setup":[],"os":null,"systems":null},"owner":{"handle":"seph1709","userId":"s173y99zb4sn7mgnw5ttzmgyc983jqrw","displayName":"seph","image":"https://avatars.githubusercontent.com/u/65636039?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1779969189219}}