Zernio
Zernio
Overview

Social Posting

XInstagramFacebookLinkedInTikTokYouTubePinterestRedditBlueskyThreadsGoogle Business ProfileSnapchat

Messaging

WhatsAppTelegramDiscordSlack

Telephony

Phone NumbersVoice & CallsSMS

Advertising

Meta AdsGoogle AdsLinkedIn AdsTikTok AdsPinterest AdsX AdsOpenAI Ads

Commerce

Shopify
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources

Instagram

Publish feed posts, carousels, Stories and Reels to Instagram with the Zernio API, with collaborators, user tags, catalog audio, paid partnership labels and location tags.


Publish feed posts, carousels, Stories and Reels to Instagram with POST /v1/posts and platform: "instagram". The same account also serves analytics, DMs, comments and comment-to-DM automations.

Quick reference

PropertyValue
Character limit2,200 (caption)
Images per post1 (feed), 10 (carousel)
Videos per post1
Image formatsJPEG, PNG
Image max size8 MB (auto-compressed)
Video formatsMP4, MOV
Video max size300 MB (feed and Reels), 100 MB (Stories)
Video max duration90 seconds (Reels), 60 minutes (feed), 60 seconds (Story)
Post typesFeed, Carousel, Story, Reel
SchedulingYes
Inbox (DMs)Yes
Inbox (comments)Yes
Comment-to-DM automationsYes
Story-reply automationsYes
AnalyticsYes

Before you start

Instagram requires a Business or Creator account; personal accounts cannot post through the API. Every post needs media, so there are no text-only posts. An account can publish 100 posts per rolling 24 hours, all content types combined; read what is left of that window with Get Instagram publishing limit and compare against the returned quotaTotal rather than hardcoding the cap. The first 125 characters of a caption show before the "more" fold.

Connect

Call GET /v1/connect/instagram with profileId and, optionally, loginMethod (connecting accounts guide). Publishing, analytics, comments and the inbox work the same with either login method; Facebook Login is required for ads scopes, catalog audio and the paid partnership label.

OAuth scopes

With Instagram Login (loginMethod=instagram_login, the default) the user authorizes their Instagram professional account directly, with no Facebook Page involved. Ads permissions exist only on Facebook Login, so this method never requests them; to run ads against such an account, connect a Facebook account in the same profile and use its token (Meta Ads).

With Facebook Login (loginMethod=facebook_login) the user authorizes a Facebook Page that has a linked Instagram professional account, and every API call for that account runs through the Page. Use it when the customer manages Instagram through a Page and expects the Facebook consent screen. Picking the Page is a second step after OAuth, in standard or headless mode (platforms requiring secondary selection). If the profile has ads access, the dialog also requests ads_management, ads_read, pages_manage_ads and leads_retrieval, so the same account can drive Meta Ads; call Connect ads with platform=instagram to create the ads account.

Instagram LoginFacebook LoginWhat it enables
instagram_business_basicinstagram_basicAccount identity and basic profile data
instagram_business_content_publishinstagram_content_publishPublish posts, Reels, Stories and carousels
instagram_business_manage_insightsinstagram_manage_insightsPost and account analytics
instagram_business_manage_commentsinstagram_manage_commentsRead and reply to comments (including the first comment)
instagram_business_manage_messagesinstagram_manage_messagesInstagram DMs in the inbox
pages_show_listList the Pages the user manages, to find the linked Instagram account
pages_read_engagementRead the linked Page
business_managementResolve Pages owned through a Business Manager

Publish

A plain post becomes a feed post when the media is an image and a Reel when it is a single video. Fields in platformSpecificData on the Instagram entry select Stories and change Reel behaviour.

Feed post

A single image or video in the main feed; no contentType is needed.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: published } = await zernio.posts.createPost({
  body: {
    content: 'Golden hour at the pier #photography',
    mediaItems: [
      { type: 'image', url: 'https://cdn.example.com/pier.jpg' }
    ],
    platforms: [
      { platform: 'instagram', accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
    ],
    publishNow: true
  }
});

console.log(published.post.platforms[0].platformPostUrl);

Response (201):

{
  "post": {
    "_id": "65f1c0a9e2b5af0012ab34cd",
    "status": "published",
    "platforms": [
      {
        "platform": "instagram",
        "status": "published",
        "platformPostUrl": "https://www.instagram.com/p/DGx7Yk2ScAb/"
      }
    ]
  }
}

Every sample below changes only the mediaItems or the platforms entry of this request.

Carousel

Up to 10 items, images and videos mixed. All items share the aspect ratio of the first item:

"mediaItems": [
  { "type": "image", "url": "https://cdn.example.com/photo1.jpg" },
  { "type": "image", "url": "https://cdn.example.com/photo2.jpg" },
  { "type": "video", "url": "https://cdn.example.com/clip.mp4" },
  { "type": "image", "url": "https://cdn.example.com/photo3.jpg" }
]

Story

contentType: "story" publishes a Story. Stories disappear after 24 hours, show no caption, and get no link sticker through Instagram's Graph API:

{
  "platform": "instagram",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "contentType": "story" }
}

Reel

A single video publishes as a Reel with no contentType. shareToFeed (default true) controls whether it also appears on the main feed:

{
  "platform": "instagram",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "shareToFeed": false }
}

Reels with catalog audio

audioConfiguration attaches a licensed music track or an original sound from Instagram's audio catalog to a Reel. It requires Facebook Login: accounts connected with Instagram Login get a 400 with code instagram_audio_requires_facebook_login until reconnected with loginMethod=facebook_login.

Find an audioId with Search Instagram audio; omit q to get what is trending:

curl -G "https://zernio.com/api/v1/accounts/66b2e19d8c3f5a7e9d0b1c2d/instagram/audio" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -d "audioType=music" \
  -d "q=summer"

Response (200):

{
  "audio": [
    {
      "audioId": "482851939985510",
      "title": "Summer Nights",
      "audioType": "music",
      "durationInMs": 182000,
      "displayArtist": "The Example Band",
      "downloadUrl": "https://scontent.cdninstagram.com/o1/v/t2/f2/m86/482851939985510.mp4"
    }
  ]
}

Original sounds carry igUsername instead of displayArtist. downloadUrl is a preview that Meta expires after roughly 1.5 days; GET /v1/accounts/{accountId}/instagram/audio/{audioId} refreshes it and re-validates a stored id before a scheduled publish. Attach the track:

{
  "platform": "instagram",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": {
    "audioConfiguration": {
      "audioId": "482851939985510",
      "audioVolume": 80,
      "videoVolume": 100
    }
  }
}

Volumes are integers from 0 to 100, default 100; videoVolume: 0 mutes the video's own sound. Stories, images and carousels reject audioConfiguration at creation. If the track becomes unavailable between scheduling and publish time (removed, region-blocked, licensing change), the post fails with an error naming the cause instead of publishing with different audio; resubmit with another track or without audioConfiguration. audioName is unrelated: it renames the video's own ("original") audio.

Platform fields

All fields go in platformSpecificData on the Instagram entry.

FieldTypeDefaultDescription
contentType"story"(feed)"story" publishes a Story. Omit otherwise: single videos publish as Reels, images go to the feed.
shareToFeedbooleantrueReels only. false shows the Reel in the Reels tab only.
collaboratorsArray<string>Up to 3 usernames of public Business or Creator accounts. Not for Stories.
userTagsArray<{username, x?, y?, mediaIndex?}>Images require x/y (0.0 to 1.0); Reels and videos ignore coordinates; Stories take them optionally. mediaIndex picks the carousel slide (0-based, default 0), video slides included.
trialParams{graduationStrategy}Trial Reels, shown only to non-followers. graduationStrategy is "MANUAL" or "SS_PERFORMANCE" (auto-graduate when it performs well).
thumbOffsetnumber (ms)0Offset from video start to use as the Reel cover. Ignored when instagramThumbnail is set.
instagramThumbnailstring (URL)Custom Reel cover, JPEG or PNG, recommended 1080 x 1920 px. Takes priority over thumbOffset. Also accepted as reelCover.
audioNamestringRenames the Reel's own audio (replaces "Original Audio"). Set at creation only.
audioConfiguration{audioId, audioVolume?, videoVolume?}Catalog track for a Reel; see Reels with catalog audio. Requires loginMethod=facebook_login.
muteAudiobooleanfalseReels, Stories and video carousel slides; ignored for images. Instagram has no mute parameter, so Zernio strips the audio track before sending and the published video is permanently silent; if stripping fails, the post fails rather than publishing with sound. Videos above 200 MB cannot be muted.
isAiGeneratedbooleanfalseInstagram labels the post as containing AI-generated images or video (not AI-written captions). Feed posts, Reels, Stories and carousels.
isPaidPartnershipbooleanfalseShows the "Paid partnership" label. Feed posts, Reels and carousels; Stories reject it at creation with a 400. Requires loginMethod=facebook_login: Instagram Login accounts get a 400 with code instagram_paid_partnership_requires_facebook_login. Implied by brandedContentSponsors. See Paid partnership label.
brandedContentSponsorsArray<string>Up to 2 sponsors, each an Instagram username (leading @ optional) or numeric Instagram user id, of public Business or Creator accounts. Same login and content-type rules as isPaidPartnership.
commentsEnabledbooleantruefalse turns comments off right after publishing (Zernio publishes first, then disables). Feed posts, Reels and carousels; ignored for Stories. Both login methods. Best-effort: if Instagram rejects the toggle, the post stays up with comments on; turn them off in the Instagram app.
locationIdstringNumeric id of a Facebook Page that has location data, not a place name. Feed posts, Reels and the carousel as a whole; not single slides, and a Story with locationId is rejected at creation with a 400. See Location tags.
firstCommentstringPosted as the first comment after publishing. Feed posts and carousels, not Stories. The place for links, because captions have none.

Paid partnership label

isPaidPartnership: true shows the label; brandedContentSponsors names the brand as well and turns the label on by itself:

{
  "platform": "instagram",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "brandedContentSponsors": ["brandpartner"] }
}

Zernio resolves sponsor usernames at publish time through Meta's Business Discovery API on the publishing account; a sponsor that cannot be resolved fails the post with an error naming it. Pass the numeric id to skip the lookup. A brand that has pre-approved you as a creator shows "Paid partnership with @brand" at once; otherwise the post publishes with the plain label, the brand receives an approval request in Instagram, and the name appears once they approve. No approval is needed to publish. Instagram does not return the label or the sponsors when reading the post back, so Get post echoes the values you sent in platformSpecificData.

Location tags

Zernio has no location search endpoint yet. Find the Page id in Facebook (the Page's "About" section or its URL) or through Meta's Pages Search API:

{
  "platform": "instagram",
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platformSpecificData": { "locationId": "105890561436614" }
}

A Page that does not exist or has no location data fails the post at publish time with "Instagram rejected locationId: the Facebook Page has no location data or does not exist."

Media requirements

Files above these limits are compressed; original files are preserved.

Images

PropertyFeed postStoryCarousel
Max images1110
FormatsJPEG, PNGJPEG, PNGJPEG, PNG
Max file size8 MB8 MB8 MB each
Recommended1080 x 1350 px1080 x 1920 px1080 x 1080 px

Feed posts and carousels accept aspect ratios from 9:16 (0.5625, 1080 x 1920 px, the tallest) through 4:5 (1080 x 1350 px, best engagement) and 1:1 (1080 x 1080 px) to 1.91:1 (1080 x 566 px, the widest). Stories and Reels are 9:16. Images outside the feed range must be posted as Stories or Reels.

Videos

PropertyFeedReelStory
FormatsMP4, MOVMP4, MOVMP4, MOV
Max file size300 MB300 MB100 MB
Max duration60 minutes90 seconds60 seconds
Min duration3 seconds3 seconds3 seconds
Aspect ratio4:5 to 1.91:19:169:16
Resolution1080 px wide1080 x 1920 px1080 x 1920 px
CodecH.264H.264H.264
Frame rate30 fps30 fps30 fps

Media URLs

A media URL must be publicly accessible with no authentication, return the media bytes with the correct Content-Type header, not redirect to an HTML page, and sit on a fast host. Google Drive, Dropbox, OneDrive and iCloud sharing links return an HTML page instead of the file, so Instagram's servers cannot fetch from them; use a direct media URL or upload through the media endpoint. Test a URL in an incognito window: a webpage instead of the raw file means the post will fail. Zernio proxies Supabase storage URLs automatically, so they work without any change.

Analytics

Call GET /v1/analytics?platform=instagram (Analytics API).

MetricAvailable
Impressions
Reach
Likes
Comments
Shares
Saves
Views

Three Instagram-only endpoints go deeper:

  • Account insights: account-level reach, views, accounts engaged, total interactions, follows and unfollows, profile link taps. Only reach supports metricType=time_series; Instagram serves every other metric as total_value only.
  • Follower history: a daily follower count series plus followers_gained and followers_lost, served from Zernio's daily snapshots because Instagram removed follower_count from /insights in Graph API v22 and never exposed a historical daily series.
  • Demographics: audience by age, city, country or gender. Requires at least 100 followers.

Stories

Two endpoints read Stories while they are live, and Zernio keeps their final metrics from Meta's story_insights webhook so they stay queryable after the 24 hours.

  • List active stories: GET /v1/accounts/{accountId}/instagram/stories returns the live stories with mediaType, permalink, mediaUrl, thumbnailUrl and timestamp. Meta excludes live videos, reshared stories and copyright-flagged media, and caption, likeCount and commentsCount do not apply to stories.
  • Story insights: GET /v1/accounts/{accountId}/instagram/stories/{storyId}/insights returns views, reach, replies, shares, profileVisits, follows, totalInteractions and the navigation breakdown (tapsForward, tapsBack, exits, swipesForward). source is live (fetched from Meta), cached (expired, webhook payload captured) or unavailable (expired, no payload, typical when the account connected after the story expired). Counts below 5 may come back as 0 because of Meta's privacy floor.

Inbox

Instagram supports DMs and comments. New comments, replies, hiding and deleting all work; liking a comment is a limited release described under Comments.

Direct messages

FeatureSupported
List conversations
Fetch messages
Send text messages
Send attachments (images, videos, audio via URL)
Quick replies (up to 13, Meta quick_replies)
Buttons (up to 3, generic template)
Carousels (generic template, up to 10 elements)
React to a message (any emoji) via Add message reaction and Remove message reaction
Message tags (HUMAN_AGENT only)
Archive and unarchive

Reactions a customer adds or removes arrive on the reaction.received webhook; accounts connected before August 2026 need their Meta webhook registration refreshed first, sending is unaffected. To message outside the 24-hour window, send messageTag: "HUMAN_AGENT" with messagingType: "MESSAGE_TAG".

Attachments:

TypeFormatsMax size
ImagePNG, JPEG8 MB
VideoMP4, OGG, AVI, MOV, WEBM25 MB
AudioAAC, M4A, WAV, MP425 MB
FilePDF25 MB

Instagram rejects every other container, including MP3 and OGG/Opus audio. Attachment URLs must be public HTTPS with no authentication or redirects and return a Content-Type matching the file, for example audio/mp4 for M4A.

Instagram profile data

Participants and webhook senders can carry an instagramProfile object: isFollower, isFollowing, followerCount, isVerified and, on conversations only, fetchedAt. It appears in GET /v1/inbox/conversations and GET /v1/inbox/conversations/{id}, on message.sender in message.received, on comment.author in comment.received when the commenter has messaged you before, and on demand from GET /v1/accounts/{accountId}/follow-status/{userId} for any Instagram-scoped user id.

Meta reveals the follow relationship only for people who have messaged you, and commenting does not grant that consent. For someone who has never sent your account a DM, follow-status returns 200 with isFollower: null and unavailableReason: "consent_required", and comment.received omits instagramProfile. Treat a missing value as unknown, never as "not a follower"; to follower-gate a comment automation, use its audience rules instead.

Ice breakers

Ice breakers are the prompts shown when someone starts a new DM conversation: up to 4, each question at most 80 characters. Manage them with GET, PUT and DELETE /v1/accounts/{accountId}/instagram-ice-breakers (Account settings).

Comments

FeatureSupported
List comments on posts
Post a new top-level comment
Reply to comments
Delete comments
Like commentsLimited release (see below)
Hide and unhide comments
Send a private reply (DM after a comment) (text plus up to 13 quick replies or 1 to 3 inline buttons, 7-day window, one per comment)

Liking a comment is in limited release. It needs Meta's instagram_manage_engagement permission, which this app holds under Standard Access, so the call works for admins, developers and testers of Zernio's Meta app and returns a 403 with code PLATFORM_BETA_RESTRICTED for everyone else. It covers comments and replies on feed posts, reels and carousels, and only on an account connected through Facebook Login: an Instagram Login connection returns a 400 with code instagram_likes_require_facebook_login.

Comment-to-DM automations

Keyword-triggered auto-DMs, created with Create comment automation. template sends a product card instead of the plain dmMessage: an image, a title, a subtitle for the description or price, and up to 3 url or postback buttons (no phone buttons); up to 10 elements render as a swipeable carousel. The card is mutually exclusive with dmMessage plus buttons, its url buttons are click-tracked like flat buttons, and it renders in the Instagram mobile app only, because Meta does not support the generic template on Instagram desktop web.

{
  "template": {
    "type": "generic",
    "elements": [{
      "imageUrl": "https://example.com/product.jpg",
      "title": "Handmade Leather Bag",
      "subtitle": "Free shipping, $49",
      "buttons": [{ "type": "url", "title": "Order Now", "url": "https://example.com/order" }]
    }]
  }
}

audience restricts who gets the DM (Instagram only; Facebook automations reject it because Meta exposes the follow relationship on Instagram only):

FieldValuesWhat it does
followerStatusany (default), follower, non_followerOnly DM followers, or only non-followers
minFollowerCountintegerSkip commenters below this follower count
whenUnknownsend (default), skip, verifyWhat to do when Instagram will not reveal the follow relationship

Because of the consent rule above, a first-time commenter is unresolvable until they message you, so whenUnknown decides most outcomes. send delivers anyway, so a real customer is never silently dropped. skip stays silent. verify sends followGate.message with a confirm button; the tap is itself a message, which grants consent, so the follow check resolves and the real DM (or followGate.notFollowingMessage) goes out. Anyone who has messaged you before is checked at once and never sees this step.

{
  "audience": { "followerStatus": "follower", "whenUnknown": "verify" },
  "followGate": {
    "message": "Follow the account, then tap below to unlock the link 👇",
    "buttonLabel": "I'm following ✅",
    "notFollowingMessage": "Looks like you're not following yet. Follow and try again 🙌"
  }
}

Webhooks

Instagram emits every message lifecycle event: message.received, message.sent, message.edited, message.deleted (the sender unsent it) and message.read. The webhooks page has the payloads. Zernio stores messages locally: live messages arrive through webhooks, and on connect Zernio replays the DM history the account already has on Meta, up to 500 conversations per account and the newest 500 messages per conversation, including conversations that began before the account was connected. The replay runs in the background, fires no webhooks, and arrives already read, so it never affects unread counts; read it from List inbox conversations.

The message.deleted payload keeps the original text and attachments, so API consumers can read pre-delete content for moderation or compliance. The Zernio dashboard hides that content.

What you cannot do

Instagram's API does not expose:

  • Story stickers (polls, questions, links, countdowns)
  • Location search by name (pass a Facebook Page id in locationId instead)
  • Going live
  • Guides
  • Filters
  • Product tags
  • Posting to personal accounts (Business or Creator only)

Common errors

ErrorCauseFix
"Cannot process video from this URL. Instagram cannot fetch videos from Google Drive, Dropbox, or OneDrive."A cloud storage sharing link instead of a direct media URLUse a direct media URL (Media URLs).
"You have reached the maximum of 100 posts per day allowed for your account."Instagram's rolling 24-hour limit, every content type countedReduce posting volume.
"Instagram blocked your request."Automation detectionReduce posting frequency, vary content, wait before retrying.
"Duplicate content detected."Identical content published recentlyChange the caption or media.
"Media fetch failed, retrying... (failed after 3 attempts)"Zernio could not download the mediaCheck that the URL is public and returns media bytes, not an HTML page.
"Instagram access token expired."The OAuth token expiredReconnect the account. The account.disconnected webhook catches this early.
No DM history after connecting (new messages still arrive)The user turned message access off on their own accountInstagram > Settings > Website permissions > Connected tools: turn it on, then reconnect.

A publishNow: true post that Instagram rejects returns 207 with post.status: "failed" and the message in platforms[].errorMessage:

{
  "message": "Post created but publishing failed",
  "error": "All platforms failed",
  "post": {
    "_id": "65f1c0a9e2b5af0012ab34cd",
    "status": "failed",
    "platforms": [
      {
        "platform": "instagram",
        "status": "failed",
        "errorMessage": "Cannot process video from this URL. Instagram cannot fetch videos from Google Drive, Dropbox, or OneDrive."
      }
    ]
  }
}

207 is a 2xx status, so fetch(...).ok is true; branch on the status code and on post.status. Error handling covers the envelope.

Related

  • Connecting accounts: the OAuth flow for Instagram Login and Facebook Login.
  • Create post: every field of the request.
  • Media uploads: upload images and videos instead of hosting them.
  • Messages and Comments: the inbox API.
  • Account settings: ice breakers.
Was this page helpful?

Limits & Errors

Every platformSpecificData field for X, what X's API does not expose, and the errors you will see with their fixes.

Facebook

Publish feed posts, multi-image posts, link carousels, Stories and Reels to a Facebook Page with the Zernio API, with drafts, first comments and country targeting.

On this page

Quick referenceBefore you startConnectOAuth scopesPublishFeed postCarouselStoryReelReels with catalog audioPlatform fieldsPaid partnership labelLocation tagsMedia requirementsImagesVideosMedia URLsAnalyticsStoriesInboxDirect messagesInstagram profile dataIce breakersCommentsComment-to-DM automationsWebhooksWhat you cannot doCommon errorsRelated