Instagram is visually demanding. The API reflects that. If you don’t follow their rules on aspect ratios and media types, they will reject your content without mercy.
Supported Content Types
- Posts: Single image, single video, or carousel (mixed images + videos). 1-10 files.
- Reels: Video only. Must be vertical.
- Stories: Image or video. Expire after 24 hours. Gone like your motivation on Monday morning.
Quirks & Gotchas
Aspect Ratios are Law
Instagram is very strict here.
- Stories/Reels: Must be 9:16 (vertical). Technically the API accepts wider ratios, but they’ll look terrible.
- Feed Posts: 4:5 (vertical) or 1:1 (square) for images. Videos are more flexible.
Don’t try to cheat. If you upload a 16:9 (landscape) video as a Reel, it might technically “work” but it will look terrible (awkwardly cropped) or be rejected entirely. Your marketing team will not be happy. Your users’ marketing teams will not be happy. Nobody will be happy.
What if I have non-standardized content?
Instagram feed Posts must fall within a specific aspect ratio range (4:5 to 1.91:1). If your users upload wide landscape photos or tall portrait shots that fall outside this range, you have two automatic correction options instead of rejecting the upload outright:
| Option | Field | Behavior |
|---|
| Fit | autoFitImage: true | Adds padding around the image to fit the target ratio. No pixels are lost. The same way Instagram does this natively. |
| Crop | autoCropImage: true | Center-crops the image to fit the target ratio. No padding, but edges are cut. |
These two options are mutually exclusive. Sending both as true will fail validation. They only apply to type: "POST" and have no effect on Reels or Stories.
Stories vs. Reels vs. Posts
- Stories are ephemeral. They disappear after 24 hours. Great for FOMO content.
- Reels are for growth. They’re public, discoverable, and the algorithm loves them.
- Posts are for the feed grid. The permanent collection.
See Platform Limits for the full breakdown of file sizes, resolutions, and aspect ratios.
Text & Field Limits
| Field | Limit |
|---|
text | Max 2,000 characters |
collaborators | Max 3 usernames, each max 30 chars |
tagged | Max 20 users per media, username max 30 chars |
carouselItems | Max 10 items |
locationId | Max 64 characters |
Instagram Misc Endpoints
- Purpose: business discovery by username (public profile data for discoverable business/creator accounts).
- Important: works only when the Instagram account is connected via
FACEBOOK login flow.
- If the account is connected via direct
INSTAGRAM method, API returns explicit 400 with reconnection guidance.
- Private accounts and some non-business/non-creator accounts may not be discoverable and can return
exists: false.
- In our tests, business discovery can sometimes return
false for accounts that are still valid for publishing tags. In other words: lookup may fail, while tagging that same username in publish payload can still work.
GET /misc/instagram/locations
- Purpose: search Facebook Pages with physical location data and return IDs for
data.INSTAGRAM.locationId.
- Important: works only for Instagram accounts connected via
FACEBOOK method.
- Uses the account page token (
socialAccount.accessToken).
- Missing page permissions/features (for example
pages_read_engagement) return explicit 400 with actionable message.
Meta location search can be inconsistent. If you hit the permission/error path, there is usually nothing we can reliably fix from API side right now (this behavior is widely reported in Meta developer threads). The good news: locationId tagging itself still works when you already have a valid location/page ID, so known IDs found externally can still be used in publish payloads. We hope this endpoint becomes stable for all accounts over time.
Publishing Payload & Validation Rules
locationId mapping
data.INSTAGRAM.locationId is mapped to location_id in Instagram publish payload.
- Supported in single image, reel/single-video flow, and carousel parent container.
shareToFeed
- Accepted only for reel-like payloads:
type = REEL, or
type = POST with exactly one video upload.
- Rejected for image posts, carousels, and stories (
400).
- Reel/single-video: provide usernames only (no
x/y coordinates).
- Single image: each tagged user must include
x and y in range 0-1.
- Carousel: top-level
tagged is rejected; use carouselItems[].tagged per item.
- Carousel video item: person tags are not supported and are rejected.
Carousel item binding
carouselItems is allowed only for real carousel posts (at least 2 uploads).
- Every
carouselItems[].uploadId must exist in provided uploadIds.
- Use each
uploadId only once in carouselItems.
{
"socialAccountTypes": ["INSTAGRAM"],
"data": {
"INSTAGRAM": {
"type": "POST",
"text": "Carousel with per-item tags",
"uploadIds": ["upl_img_1", "upl_img_2", "upl_vid_1"],
"carouselItems": [
{
"uploadId": "upl_img_1",
"tagged": [{ "username": "alice", "x": 0.25, "y": 0.4 }]
},
{
"uploadId": "upl_img_2",
"tagged": [{ "username": "bob", "x": 0.6, "y": 0.35 }]
},
{
"uploadId": "upl_vid_1"
}
]
}
}
}
Story constraints
- Stories do not support
collaborators.
- Stories do not support
locationId.
Collaborators behavior
- Collaborators are normalized before publish (
@ stripped, trim, lowercase).
- Automatic fallback retry without unavailable collaborators was removed.
- Retry behavior now follows standard BullMQ worker retry policy.
Trial Reels
Trial reels are shared only with non-followers first. If the content performs well, it can be graduated to a regular reel that appears to everyone.
Send data.INSTAGRAM.trialParams.graduationStrategy in the publish payload:
| Value | Behavior |
|---|
MANUAL | The trial reel stays in trial until you manually graduate it from the native Instagram app. |
SS_PERFORMANCE | Instagram automatically graduates the reel if it meets a performance threshold. |
Requirements and caveats:
- Only valid for
type: "REEL". Ignored for Posts and Stories.
- Requires a public professional (Creator or Business) account with at least around 1,000 followers. Instagram has not published an official threshold and some users report access starting at 200 followers.
- Not all professional accounts will have access to this feature regardless of follower count.
{
"socialAccountTypes": ["INSTAGRAM"],
"data": {
"INSTAGRAM": {
"type": "REEL",
"text": "Testing this reel with non-followers first",
"uploadIds": ["upl_vid_1"],
"trialParams": {
"graduationStrategy": "SS_PERFORMANCE"
}
}
}
}
Analytics
For general analytics concepts (refresh rates, data retention, what “Returns 0” means), see the Analytics Overview.
Refresh Rate & Limits
- Default Refresh: Every 24 hours.
- Force Refresh: Available (max
Teams × 5 per day).
- Data Retention: 30 days. Details.
Profile Analytics
Period: Rolling window (30 days).
| Metric | Description | Note |
|---|
impressions | Total times profile content was shown | |
impressionsUnique | Unique accounts that saw content | Called “Reach” by Instagram |
views | Profile views | |
viewsUnique | - | Returns 0 (not provided by API) |
likes | Total likes | |
comments | Total comments | |
postCount | Total posts published | |
followers | Total followers | |
following | Total following | |
Post Analytics
Period: Lifetime (Snapshot).
| Metric | Description | Note |
|---|
impressions | Total views | Called “views” in Instagram API |
impressionsUnique | Unique accounts reached | Called “reach” in Instagram API |
views | Video plays | Videos/Reels only |
viewsUnique | - | Returns 0 (not provided by API) |
likes | Total likes | |
comments | Total comments | |
shares | Total shares | Reels only |
saves | Total saves | |
Raw Analytics - Demographics & Audience Data
Instagram provides some of the richest demographic data via raw analytics. Here’s what the raw payload looks like when enabled for your organization:
{
"profileInfo": { /* basic account info */ },
"totals30d": [ /* 30-day daily breakdown */ ],
"demographics": {
"follows_and_unfollows": {
"data": [{
"name": "follows_and_unfollows",
"period": "day",
"title": "Follows and unfollows",
"total_value": { "value": { "follows": 142, "unfollows": 23 } }
}]
},
"follower_demographics": {
"country": {
"data": [{
"name": "follower_demographics",
"total_value": {
"breakdowns": [{
"dimension_keys": ["country"],
"results": [
{ "dimension_values": ["US"], "value": 4521 },
{ "dimension_values": ["GB"], "value": 1893 },
{ "dimension_values": ["DE"], "value": 1247 }
]
}]
}
}]
},
"city": {
"data": [{
"name": "follower_demographics",
"total_value": {
"breakdowns": [{
"dimension_keys": ["city"],
"results": [
{ "dimension_values": ["New York, New York"], "value": 892 },
{ "dimension_values": ["London, England"], "value": 654 }
]
}]
}
}]
},
"gender": {
"data": [{
"name": "follower_demographics",
"total_value": {
"breakdowns": [{
"dimension_keys": ["gender"],
"results": [
{ "dimension_values": ["M"], "value": 5234 },
{ "dimension_values": ["F"], "value": 4891 },
{ "dimension_values": ["U"], "value": 312 }
]
}]
}
}]
},
"age": {
"data": [{
"name": "follower_demographics",
"total_value": {
"breakdowns": [{
"dimension_keys": ["age"],
"results": [
{ "dimension_values": ["18-24"], "value": 3421 },
{ "dimension_values": ["25-34"], "value": 4102 },
{ "dimension_values": ["35-44"], "value": 1893 },
{ "dimension_values": ["45-54"], "value": 721 }
]
}]
}
}]
}
},
"engaged_audience_demographics": {
"country": { /* same structure as follower_demographics */ },
"city": { /* same structure */ },
"gender": { /* same structure */ },
"age": { /* same structure */ }
}
}
}
Raw demographics include both follower demographics (lifetime, who follows you) and engaged audience demographics (this month, who interacts with your content). These are two different audiences and both are useful for understanding your reach.