Platforms
Publish feed posts, carousels, Stories and Reels to Instagram, read account and post insights, and answer DMs and comments.
Connect an Instagram professional account and you can publish every format Instagram's API allows: feed images, carousels, Stories and Reels. The same account gives you post and account insights, the DM inbox, comment moderation and comment-to-DM automations. The platform id is instagram.
#What you can do
| Capability | Supported | Where |
|---|---|---|
| Publish | Feed images, carousels (up to 10 items), Stories, Reels, trial Reels | Create a post |
| Schedule | Yes, every format including Stories | Scheduling |
| Analytics | Post metrics, account insights, follower history, demographics, online followers, Story insights | Platform insights |
| DMs | Read and reply, attachments, quick replies, buttons, product cards, reactions | Messages |
| Comments | List, reply, hide, delete, private reply | Comments |
| Automations | Comment, live comment, Story reply and Story mention triggers | Comment to DM |
| Ads | Through a Meta Ads connection | Meta Ads |
#Connect
Instagram needs a Business or Creator account. Personal accounts cannot publish through any third-party API, so ask the user to switch their account type in the Instagram app first (it is free and takes a minute).
Start the connection with GET /v1/connect/instagram and a profileId. There are two login methods, chosen with the loginMethod query parameter:
loginMethod |
What the user signs in to | Use it when |
|---|---|---|
instagram_login (default) |
Their Instagram professional account directly. No Facebook Page needed. | You only need publishing, insights and the inbox. |
facebook_login |
A Facebook Page that has the Instagram account linked. Calls run through that Page. | You also need ads, catalog audio on Reels, the paid partnership label or liking comments. |
With facebook_login the user picks the Page in a second step after OAuth. Pozy can host that picker for you, or you can build it yourself with listInstagramPages and selectInstagramAccount. See Headless connect.
curl "https://api.pozy.ai/v1/connect/instagram?profileId=6720c0f24f3b2e0018d4a6e0&loginMethod=facebook_login&redirect_url=https://app.example.com/connected" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/connect/instagram?profileId=6720c0f24f3b2e0018d4a6e0&loginMethod=facebook_login&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// Send the user's browser to data.authUrlimport os, requests
resp = requests.get(
"https://api.pozy.ai/v1/connect/instagram",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={
"profileId": "6720c0f24f3b2e0018d4a6e0",
"loginMethod": "facebook_login",
"redirect_url": "https://app.example.com/connected",
},
)
print(resp.json()["authUrl"])Pass scopes to request only the permission areas you need (for example publishing and analytics, without ads). Fewer permissions on the consent screen means fewer users who stop halfway. See Scopes.
#Post types
Instagram decides the format from the media you send, with one field to override it. Every post needs media: there are no text-only Instagram posts.
| You send | Instagram publishes | Extra fields |
|---|---|---|
| One image | Feed post | altText, userTags, collaborators, locationId, firstComment |
| 2 to 10 images or videos | Carousel | Same as feed. Every item takes the aspect ratio of the first one. |
| One video | Reel | shareToFeed, instagramThumbnail or thumbOffset, audioName, audioConfiguration, trialParams |
Any single image or video with contentType: "story" |
Story | userTags (coordinates optional) |
#Feed post
curl -X POST https://api.pozy.ai/v1/posts \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "First light over the harbour. #morning",
"mediaItems": [
{ "type": "image", "url": "https://cdn.example.com/harbour.jpg", "altText": "Fishing boats at dawn" }
],
"platforms": [
{ "platform": "instagram", "accountId": "6720c1a94f3b2e0018d4a7f1" }
],
"publishNow": true
}'const res = await fetch("https://api.pozy.ai/v1/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
content: "First light over the harbour. #morning",
mediaItems: [
{ type: "image", url: "https://cdn.example.com/harbour.jpg", altText: "Fishing boats at dawn" },
],
platforms: [{ platform: "instagram", accountId: "6720c1a94f3b2e0018d4a7f1" }],
publishNow: true,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.post.platforms[0].platformPostUrl);import os, requests
resp = requests.post(
"https://api.pozy.ai/v1/posts",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"content": "First light over the harbour. #morning",
"mediaItems": [
{"type": "image", "url": "https://cdn.example.com/harbour.jpg", "altText": "Fishing boats at dawn"}
],
"platforms": [{"platform": "instagram", "accountId": "6720c1a94f3b2e0018d4a7f1"}],
"publishNow": True,
},
)
print(resp.json()["post"]["platforms"][0]["platformPostUrl"])altText applies to feed images and carousel images. Instagram cuts it at 1,000 characters and ignores it on Reels, Stories and video slides.
#Carousel
Put up to 10 images and videos in mediaItems. Mixed media is fine. Crop everything to the same ratio before you upload, because Instagram uses the first item's ratio for all of them.
"mediaItems": [
{ "type": "image", "url": "https://cdn.example.com/menu-1.jpg" },
{ "type": "image", "url": "https://cdn.example.com/menu-2.jpg" },
{ "type": "video", "url": "https://cdn.example.com/kitchen.mp4" }
]#Reel
A single video is always a Reel. Set shareToFeed: false to keep it in the Reels tab only, and give it a cover with instagramThumbnail (a JPEG or PNG URL, 1080 x 1920 px works best) or a frame offset in milliseconds with thumbOffset.
{
"platform": "instagram",
"accountId": "6720c1a94f3b2e0018d4a7f1",
"platformSpecificData": {
"shareToFeed": true,
"instagramThumbnail": "https://cdn.example.com/reel-cover.jpg",
"audioName": "Studio session"
}
}Trial Reels are shown to non-followers first. Add trialParams.graduationStrategy: MANUAL (you promote it yourself in the app) or SS_PERFORMANCE (Instagram shares it with followers if it performs well).
Catalog audio. audioConfiguration attaches a track from Instagram's audio library. Find an audioId with Search Instagram audio (leave out q to get what is trending), then send it with optional audioVolume and videoVolume from 0 to 100. This needs facebook_login. If the track is pulled before a scheduled Reel goes out, the post fails with a message naming the cause rather than publishing with different sound. Get Instagram audio metadata re-checks a stored id before publish time.
muteAudio: true removes the video's own sound track before upload, so the result is permanently silent. Videos over 200 MB cannot be muted.
#Story
Add contentType: "story". Stories show no caption, last 24 hours and cannot carry link stickers through the API. A Story publishes one media item, so give a Story entry its own customMedia when the post has several.
To publish a feed post and a Story of the same campaign at once, list the account twice:
"platforms": [
{ "platform": "instagram", "accountId": "6720c1a94f3b2e0018d4a7f1" },
{
"platform": "instagram",
"accountId": "6720c1a94f3b2e0018d4a7f1",
"customMedia": [{ "type": "image", "url": "https://cdn.example.com/story-1080x1920.jpg" }],
"platformSpecificData": { "contentType": "story" }
}
]Each entry publishes and reports its status separately.
#Platform fields
All of these go in platformSpecificData on the Instagram entry (schema: InstagramPlatformData).
| Field | Type | Applies to | Notes |
|---|---|---|---|
contentType |
"story" |
Story | Leave out for feed posts and Reels. |
shareToFeed |
boolean, default true |
Reels | false keeps the Reel off the main grid. |
collaborators |
array of usernames | Feed, carousel, Reel | Up to 3 public Business or Creator accounts. They get an invite to co-author. |
userTags |
array of { username, x, y, mediaIndex } |
All | x and y run from 0 to 1 and are required on images. mediaIndex picks the carousel slide. |
firstComment |
string | Feed, carousel, Reel | Posted right after publishing. Captions cannot hold clickable links, so this is where links usually go. |
locationId |
string | Feed, carousel, Reel | The numeric id of a Facebook Page that has an address. Not a place name. Stories reject it. |
trialParams |
{ graduationStrategy } |
Reels | MANUAL or SS_PERFORMANCE. |
instagramThumbnail / reelCover |
URL | Reels | Custom cover. Wins over thumbOffset. |
thumbOffset |
integer (ms) | Reels | Frame to use as the cover. |
audioName |
string | Reels | Renames the original audio. Set at creation only. |
audioConfiguration |
{ audioId, audioVolume, videoVolume } |
Reels | Catalog track. Needs facebook_login. |
muteAudio |
boolean | Reels, Stories, video slides | Strips the sound before upload. |
isAiGenerated |
boolean | All | Adds Instagram's AI label for generated images or video. |
isPaidPartnership |
boolean | Feed, carousel, Reel | Shows the "Paid partnership" label. Needs facebook_login. |
brandedContentSponsors |
array, up to 2 | Feed, carousel, Reel | Usernames or numeric ids of the brands. Turns the label on by itself. The brand is named once they approve the request in Instagram. |
commentsEnabled |
boolean, default true |
Feed, carousel, Reel | false turns comments off right after publishing. |
If a collaborator cannot be invited, the post still publishes and the name comes back in droppedCollaborators with a reason.
#Media requirements
Pozy compresses images over 8 MB and keeps your original. Media URLs must return the file itself: sharing links from Google Drive, Dropbox, OneDrive or iCloud return a web page and fail. Upload files with Media uploads when you do not have a direct URL.
Images
| Feed and carousel | Story | |
|---|---|---|
| Formats | JPEG, PNG | JPEG, PNG |
| Max size | 8 MB each | 8 MB |
| Aspect ratio | 4:5 (1080 x 1350) to 1.91:1 (1080 x 566); 1:1 and 9:16 also accepted | 9:16 (1080 x 1920) |
| Count | 1, or up to 10 in a carousel | 1 |
Videos
| Reel | Story | |
|---|---|---|
| Formats | MP4, MOV (H.264) | MP4, MOV (H.264) |
| Max size | 300 MB | 100 MB |
| Duration | 3 seconds to 15 minutes | 3 to 60 seconds |
| Aspect ratio | 9:16, 1080 x 1920 | 9:16, 1080 x 1920 |
| Frame rate | 30 fps recommended | 30 fps recommended |
#Limits
| Limit | Value |
|---|---|
| Caption | 2,200 characters. About 125 show before "more". |
| Posts per account | 100 per rolling 24 hours, all formats combined |
| Collaborators | 3 per post |
| Branded content sponsors | 2 per post |
| Alt text | 1,000 characters |
Read how much of the 24-hour allowance is left with Get Instagram publishing limit. Compare quotaUsage with the returned quotaTotal instead of hardcoding 100, since Instagram can change the cap.
Instagram's API does not support Story stickers (polls, links, countdowns), product tags, filters, Guides or going live. Published Instagram posts cannot be edited or deleted through the API; change them in the Instagram app.
#Analytics
GET /v1/analytics?platform=instagram returns per-post impressions, reach, likes, comments, shares, saves and views, plus Reel watch-time fields (igReelsAvgWatchTime, igReelsVideoViewTotalTime) where Instagram reports them.
Account-level endpoints go further:
| Endpoint | What you get |
|---|---|
| Instagram insights | Reach, views, accounts engaged, interactions, follows and unfollows, profile views, link taps. Only reach comes as a time series. |
| Follower history | Daily follower count with followers_gained and followers_lost, built from daily snapshots. |
| Demographics | Audience by age, gender, city or country. Needs at least 100 followers. |
| Online followers | When followers are online, by hour. Useful with Best time to post. |
| Active Stories and Story insights | Live Stories with views, reach, replies, taps forward and back, exits. |
Story numbers are only readable while the Story is live. After 24 hours you get final numbers only if Instagram sent them before expiry, which happens far more reliably on accounts connected with facebook_login. If you need final Story metrics, read them before the Story expires. Counts under 5 can come back as 0 because of Instagram's privacy floor.
Image and cover URLs in analytics responses are served from Pozy storage and do not expire, so you can cache them.
#Inbox
Direct messages. List conversations, read messages and reply with Send message. You can send text, images (PNG, JPEG up to 8 MB), video and audio (up to 25 MB), PDFs, up to 13 quick replies, up to 3 buttons (the text must then be 640 characters or less) and product cards of up to 10 elements. Reactions work both ways.
You can message someone freely for 24 hours after their last message. After that, a human-typed reply can still go out for up to 7 days with messagingType: "MESSAGE_TAG" and messageTag: "HUMAN_AGENT". Instagram does not allow that tag on automated messages, so workflows and broadcasts refuse it.
Instagram does not expose the Message Requests folder separately, so GET /v1/inbox/conversations?folder=requests returns 400 with code PLATFORM_LIMITATION. Requests still reach you as message.received once the person writes, and Accept a message request works on them.
When another app owns a thread through Instagram's handover protocol, inbound messages arrive with metadata.standby: true, automations stay quiet and sends return 409 not_thread_owner. Move the thread with thread control.
Conversations and senders can carry an instagramProfile object (isFollower, isFollowing, followerCount, isVerified). Instagram only reveals the follow relationship for people who have messaged the account, so treat a missing value as unknown, not as "not a follower".
When you connect an account, Pozy imports its existing DM history (up to 500 conversations, the newest 500 messages in each). The import runs in the background, arrives already read and fires no webhooks.
Comments. List comments, post a top-level comment, reply, hide, unhide and delete. Send private reply opens a DM from a comment, once per comment, within 7 days. Liking a comment is in limited release on Instagram's side and only works on facebook_login accounts; other accounts get 403 PLATFORM_BETA_RESTRICTED or 400 instagram_likes_require_facebook_login.
Conversation starters. Set up to 4 ice breakers (80 characters each) and a persistent menu (3 top-level items, 5 nested). A menu postback with payload pozy:workflow:<workflowId> starts that workflow. See Conversation starters.
#Errors you may see
| Message | Cause | Fix |
|---|---|---|
| Cannot process video from this URL | A cloud-drive sharing link instead of a direct file URL | Use a direct URL or upload through Media. |
| Maximum of 100 posts per day reached | The rolling 24-hour cap | Spread posts out. Check the publishing limit. |
| Instagram blocked your request | Instagram's automation detection | Post less often, vary the content, wait before retrying. |
| Duplicate content detected | Same caption and media posted recently | Change the caption or media. |
instagram_audio_requires_facebook_login |
audioConfiguration on an Instagram Login account |
Reconnect with loginMethod=facebook_login. |
instagram_paid_partnership_requires_facebook_login |
Paid partnership label on an Instagram Login account | Reconnect with loginMethod=facebook_login. |
| Instagram rejected locationId | The Page id has no address or does not exist | Use the id of a Page with location data. |
| Access token expired | The user's session ended or they removed access | Reconnect the account. Listen for account.disconnected. |
When a post sent with publishNow: true is rejected, the response is 207 with post.status: "failed" and the reason in platforms[].errorMessage. A 207 is still a 2xx, so check post.status rather than only the HTTP status.
#Endpoints
#Webhook events
| Event | When it fires |
|---|---|
post.published |
Post published. |
post.failed |
Post failed. |
post.platform.published |
Post platform published. |
post.platform.failed |
Post platform failed. |
post.platform.deleted |
Post platform deleted. |
account.disconnected |
Account disconnected. |
message.received |
Message received. |
conversation.control_changed |
Conversation control changed. |
conversation.started |
Conversation started. |
message.sent |
Message sent. |
message.edited |
Message edited. |
message.deleted |
Message deleted. |
message.delivered |
Message delivered. |
message.read |
Message read. |
message.played |
Message played. |
message.failed |
Message delivery failed. |
reaction.received |
Reaction received. |
comment.received |
Comment received. |