Skip to main content

Quick reference

FieldValue
Caption limit2,200 characters
Hashtags per post30 max
Mentions per post20 max
Carousel size2–10 items
Mixed media in carousel✅ (image + video)
Video formatsMP4, MOV
Image formatsJPEG, PNG
Max video size100 MB (Feed), 1 GB (Reels)
Max image size30 MB
Reel duration3–90 seconds
Feed video duration3 seconds – 60 minutes
Aspect ratios4:5 to 1.91:1 (Feed), 9:16 (Reel)
Post typesFeed (single, carousel), Reel
First comment✅ Auto-posts after publish
Account typesBusiness + Creator only (no Personal)

Before you start

Instagram personal accounts cannot publish via API — only Business and Creator accounts are eligible. The connect flow detects this and won’t complete OAuth for unsupported account types.The connected Instagram account must also be linked to a Facebook Page that you administer; that’s how Meta authorizes the publish action.
The connected Instagram account must:
  • Be a Business or Creator account (not Personal).
  • Be linked to a Facebook Page you administer.
  • Have granted scopes instagram_business_basic, instagram_business_content_publish, and instagram_business_manage_comments (the latter unlocks first-comment).
The first-comment scope was added in May 2026. Accounts connected before that date have to reconnect to use first-comment — the API surfaces this with the IG_SCOPE_MISSING error.

Quick start

Schedule a single-image Feed post 5 minutes from now using the flat body shape (content + platforms). The workspace is inferred from your API key — no workspaceId argument needed. The example uses mediaItems to ingest a public image URL on the fly; see Media uploads for the pre-upload alternative.
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

const post = await postbreeze.posts.create({
  content: "Launching the new collection today 🚀",
  scheduledFor: new Date(Date.now() + 5 * 60_000).toISOString(),
  mediaItems: [{ type: "image", url: "https://cdn.example.com/launch.jpg" }],
  platforms: [{ accountId: "soc_instagram_…" }],
});

console.log("Scheduled:", post.id);
Need fine control? Switch to the nested shape (caption + targets) when you want to set platformOptions per-platform, send different media per platform, or set a kind: "REEL" discriminator — see Full control at the bottom. The flat shape’s platformOptions per-entry handles most cases though — see Platform settings.

Content types

Single-image or single-video Feed post

Default for any single-item Feed post. kind defaults to "FEED" and exactly one media item is attached. Pass 2–10 pre-uploaded mediaIds (or 2–10 mediaItems for URL ingest). Instagram crops every slide to the first slide’s aspect ratio — so if you start with a 4:5 image and follow it with a 1:1 image, the second gets top + bottom cropped without warning. Postbreeze surfaces a per-slide warning at compose-time when this is detected.
const post = await postbreeze.posts.create({
  content: "Behind the scenes from the shoot ✨",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_1", "med_2", "med_3", "med_4"],
  platforms: [{ accountId: "soc_instagram_…" }],
});
Prefer URL ingest for a quick test? Swap mediaIds for mediaItems with per-item altText:
await postbreeze.posts.create({
  content: "Behind the scenes from the shoot ✨",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [
    { type: "image", url: "https://cdn.example.com/shot-1.jpg", altText: "Producer setting up lights" },
    { type: "image", url: "https://cdn.example.com/shot-2.jpg", altText: "Stylist adjusting outfit" },
    { type: "image", url: "https://cdn.example.com/shot-3.jpg" },
    { type: "image", url: "https://cdn.example.com/shot-4.jpg" },
  ],
  platforms: [{ accountId: "soc_instagram_…" }],
});

Reel

Set kind: "REEL" via platformOptions on the platform entry. The attached media must be a video between 3–90 seconds, 9:16 aspect ratio. Reels are also shared to the Feed by default.
const post = await postbreeze.posts.create({
  content: "30-second product demo",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_video_…"],
  platforms: [{
    accountId: "soc_instagram_…",
    platformOptions: { platform: "INSTAGRAM", kind: "REEL" },
    firstComment: "Drop a 🔥 if you want a longer version!",
  }],
});

Media requirements

Images

PropertyRequirement
Max items10 per carousel, 1 for single posts
FormatsJPEG, PNG
Max file size30 MB
Aspect ratio4:5 to 1.91:1
Min resolution320 × 320
Max resolution8192 × 8192

Videos

PropertyRequirement
FormatsMP4, MOV
Max file size (Feed)100 MB
Max file size (Reel)1 GB
Min duration3 seconds
Max duration (Feed)60 minutes
Max duration (Reel)90 seconds
Aspect ratio (Feed)4:5 to 1.91:1
Aspect ratio (Reel)9:16
CodecsH.264 + AAC
Resolution (Reel)1080 × 1920 recommended
Postbreeze itself accepts images (JPG, PNG, GIF, WebP, HEIC, HEIF) and videos (MP4, MOV, AVI, WebM) up to 5 GB at upload time — the tighter caps above are Instagram’s publish-time limits. See Media uploads for the full ingest pipeline.

Platform-specific fields

FieldTypeRequiredDescription
platform"INSTAGRAM"YesDiscriminator.
kind"FEED" | "REEL"No (default FEED)Picks the publish surface. Single-video posts can be either; multi-item must be FEED.
Instagram has no other publish-time settings. Use firstComment at the platform-entry level (not inside platformOptions) for the auto-comment. See Platform settings for the full reference.

First comment

Pass firstComment on the platform entry. Postbreeze waits 3 seconds after the main post lands (Instagram occasionally 4xx’s with media not available on faster calls) and then posts the comment as the same authenticated user.
platforms: [{
  accountId: "soc_instagram_…",
  platformOptions: { platform: "INSTAGRAM", kind: "FEED" },
  firstComment: "#launch #behindthescenes #brand",
}]
Comment limit: 2,200 characters (same as caption). If the comment call fails (e.g. rate limit), the main post still shows as PUBLISHED and the failure surfaces on PostTarget.firstCommentError. Use the retry endpoint to try the comment again.

Analytics

MetricAvailable
Impressions
Reach
Likes
Comments
Saves
Shares
Profile visits
Follows from post
Video views (Reels)
Avg watch time (Reels)
Story-only metrics❌ (Stories not yet supported)
Refresh cadence: every 14 days per Meta’s Insights guidance.

Common errors

ErrorMeaningFix
IG_CONTAINER_EXPIREDThe container was created but not published within 24hRe-create — Postbreeze does this automatically on retry.
IG_CAROUSEL_SIZECarousel had fewer than 2 or more than 10 itemsTrim to 2–10.
IG_REEL_REQUIRES_VIDEOkind: "REEL" but no video attachedAttach a video.
IG_FEED_REQUIRES_IMAGESingle-item Feed post with a non-imageAttach an image or set kind: "REEL".
IG_MEDIA_NOT_READYComment call fired before IG finished processing the mediaPostbreeze retries with backoff. Resolved automatically.
IG_SCOPE_MISSINGAccount doesn’t have instagram_business_manage_commentsReconnect the account; the compose UI shows a banner.
IG_ASPECT_RATIOImage or video outside 4:5–1.91:1Crop/resize before uploading.
IG_DURATION_OUT_OF_RANGEReel < 3s or > 90sTrim before uploading.

What you can’t do

  • ❌ Publish to Personal Instagram accounts (Business/Creator only)
  • ❌ Stories (planned for v2)
  • ❌ Tag products or shoppable posts
  • ❌ Tag users in caption (you can include @handle but no structured tagging)
  • ❌ Add location tags (Meta removed this from the API)
  • ❌ Schedule via Instagram’s native scheduler
  • ❌ Edit captions after publish
  • ❌ Live video, IGTV

Full control (nested shape)

The nested shape (caption + targets) is the long-form alternative to the flat shape used everywhere else on this page. Reach for it when you want different captions per platform, different media per platform in the same request, or just prefer the explicit socialAccountId / platformOptions naming. The two shapes are interchangeable — every example above can be rewritten in the nested form by renaming contentcaption, scheduledForscheduledAt, platformstargets, and accountIdsocialAccountId.
SDK
await postbreeze.posts.create({
  caption: "Behind the scenes from the shoot ✨",
  scheduledAt: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_1", "med_2", "med_3"],
  targets: [
    {
      socialAccountId: "soc_instagram_…",
      platformOptions: { platform: "INSTAGRAM", kind: "FEED" },
    },
  ],
});